Live Patching
 help / color / mirror / Atom feed
From: Song Liu <song@kernel.org>
To: live-patching@vger.kernel.org
Cc: jpoimboe@kernel.org, peterz@infradead.org, jikos@kernel.org,
	mbenes@suse.cz, pmladek@suse.com, joe.lawrence@redhat.com,
	puranjay@kernel.org, kernel-team@meta.com,
	Song Liu <song@kernel.org>
Subject: [PATCH v4 10/58] objtool: Keep failing test workdirs by default
Date: Tue, 15 Sep 2026 16:34:06 -0700	[thread overview]
Message-ID: <20260915233454.2405019-11-song@kernel.org> (raw)
In-Reply-To: <20260915233454.2405019-1-song@kernel.org>

From: Joe Lawrence <joe.lawrence@redhat.com>

The klp test runner deletes each test's working directory when the test
finishes.  On failure that removes diff.log, out.o and the fixture objects
before the developer can look at them, and the only hint was to re-run
(something) with --keep:

  $ make -C tools/objtool tests
  ...
  not ok - test-special-section: expected section '.kcfi_traps' in output
  ...
  # pass:49 fail:1 static-skip:1 probe-skip:0 xfail:0 xpass:0
  # re-run with --keep to hold on to what a failing test built

Change the default behavior to save failing test working directories. This
is friendlier to the drive-by tester.  Provide the power-user with a KEEP
Makefile variable that can optionally retain 'all' or 'none' of those
directories.  run-tests.sh --keep remains an alias for KEEP=all.

Now that a run can be holding objects worth looking at, say where they are
when it is cut short.  A Ctrl-C left the run directory behind -- the tests
clean up after themselves as they exit, but the two levels the runner made
are its own to answer for, and nothing removed them.  Take them away from a
trap if the tests left them empty, and print the path if they did not: under
the new default the test that was running keeps what it had built, and that
is the run most worth being told about.

The trap only ever rmdirs, so it cannot overrule what a test decided to
keep; the same helper now serves the end of a normal run.

Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
Assisted-by: Claude:claude-opus-5
Signed-off-by: Song Liu <song@kernel.org>
---
 .../objtool/Documentation/klp-test-design.txt | 14 ++-
 tools/objtool/Makefile                        |  2 +-
 tools/objtool/tests/lib.sh                    | 62 ++++++++++---
 tools/objtool/tests/run-tests.sh              | 92 +++++++++++++++----
 4 files changed, 136 insertions(+), 34 deletions(-)

diff --git a/tools/objtool/Documentation/klp-test-design.txt b/tools/objtool/Documentation/klp-test-design.txt
index ec56bfbc2c93..67d20bf0dc10 100644
--- a/tools/objtool/Documentation/klp-test-design.txt
+++ b/tools/objtool/Documentation/klp-test-design.txt
@@ -199,10 +199,11 @@ source layout::
         generic/test-basic/{orig.o,patched.o,out.o,Module.symvers,...}
         x86/test-kcfi/...
 
-``--keep`` leaves it and reports the one path.  Otherwise each test removes its
-own directory and the runner ``rmdir``s the run's -- which fails if anything
-was left behind, so a test which dies without cleaning up is reported rather
-than silently leaking.
+By default (``KEEP=failed``) only failing tests keep their directories; the
+runner reports where they are.  ``KEEP=all`` keeps every test's directory;
+``KEEP=none`` removes them all.  The runner ``rmdir``s the run directory when
+it is empty -- which fails if anything was left behind unexpectedly, so a test
+which dies without cleaning up is reported rather than silently leaking.
 
 
 Running
@@ -216,7 +217,10 @@ Running
     CC=clang make -C tools/objtool tests  # the other toolchain
     LLVM=1 make -C tools/objtool tests    # and its binutils too
 
-    tools/objtool/tests/run-tests.sh --keep basic   # one test, keep its objects
+    make -C tools/objtool tests KEEP=all    # keep every test's workdir
+    make -C tools/objtool tests KEEP=none   # remove all workdirs
+
+    tools/objtool/tests/run-tests.sh basic  # one test; failures kept by default
 
 A run covers one compiler and one architecture; CI runs the combinations.
 
diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile
index 2c200d05c276..ed5cf146ef2e 100644
--- a/tools/objtool/Makefile
+++ b/tools/objtool/Makefile
@@ -153,7 +153,7 @@ mrproper: clean
 
 tests: $(OBJTOOL)
 	$(Q)OBJTOOL=$(abspath $(OBJTOOL)) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) \
-		$(srctree)/tools/objtool/tests/run-tests.sh
+		KEEP=$(KEEP) $(srctree)/tools/objtool/tests/run-tests.sh
 
 FORCE:
 
diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh
index 9c3d5fbded9e..bf3291b9ef9b 100644
--- a/tools/objtool/tests/lib.sh
+++ b/tools/objtool/tests/lib.sh
@@ -158,8 +158,8 @@ workdir=
 orig_obj=orig.o
 patched_obj=patched.o
 
-pass() { echo "ok - $test_name${1:+: $1}"; exit 0; }
-fail() { echo "not ok - $test_name: $1"; exit 1; }
+pass() { KLP_TEST_REPORTED=1; echo "ok - $test_name${1:+: $1}"; exit 0; }
+fail() { KLP_TEST_FAILED=1; echo "not ok - $test_name: $*"; exit 1; }
 
 # Two kinds of skip, and the runner tells them apart.
 #
@@ -172,8 +172,19 @@ fail() { echo "not ok - $test_name: $1"; exit 1; }
 #
 # A bare skip() is neither, and the runner counts it as a failure: a test which
 # gives up for a reason it never declared is a hole, not an outcome.
-declared_skip() { echo "ok - $test_name # SKIP (declared) $*"; exit 0; }
-probe_skip()    { echo "ok - $test_name # SKIP (probe) $*"; exit 0; }
+declared_skip()
+{
+	KLP_TEST_REPORTED=1
+	echo "ok - $test_name # SKIP (declared) $*"
+	exit 0
+}
+
+probe_skip()
+{
+	KLP_TEST_REPORTED=1
+	echo "ok - $test_name # SKIP (probe) $*"
+	exit 0
+}
 skip()          { echo "ok - $test_name # SKIP $*"; exit 0; }
 
 # TAP directives.  A test which is known to fail reports it rather than being
@@ -182,26 +193,55 @@ skip()          { echo "ok - $test_name # SKIP $*"; exit 0; }
 # which is the point.
 xfail()
 {
+	KLP_TEST_REPORTED=1
 	echo "not ok - $test_name${1:+: $1} # TODO known failure"
 	exit 0
 }
 
 xpass()
 {
+	KLP_TEST_FAILED=1
 	echo "ok - $test_name${1:+: $1} # TODO expected failure, but passed"
 	exit 1
 }
 
+# cleanup [exit]
+#
+# Called with "exit" from the trap, when the test is over and what it built may
+# be worth keeping.  Called bare by a test which has finished with one segment
+# and is about to setup() another: that one is done with, whatever the outcome
+# of the segments still to come, so it goes.
 cleanup()
 {
 	[ -n "$workdir" ] || return 0
 
-	if [ -n "${KLP_TEST_KEEP:-}" ]; then
-		[ -n "${KLP_TEST_WORKDIR:-}" ] || echo "# kept $workdir"
-		return 0
-	fi
-
-	rm -rf "$workdir"
+	# run-tests.sh exports KLP_TEST_KEEP, having validated it; a test run on
+	# its own reads KEEP itself, so the same setting means the same thing
+	# either way.
+	case "${KLP_TEST_KEEP:-${KEEP:-failed}}" in
+	all)	return 0 ;;
+	none)	rm -rf "$workdir" ;;
+	failed|*)
+		[ "${1:-}" = exit ] || {
+			rm -rf "$workdir"
+			return 0
+		}
+		# Keep what the runner is going to point at.  It counts as a
+		# failure anything which did not report an expected outcome --
+		# including a test which died before printing one, and an
+		# undeclared skip -- and none of those set KLP_TEST_FAILED, so
+		# the question to ask is whether a result was reported at all.
+		# An exit status cannot answer it: a test killed by a signal
+		# runs this trap with the status of whatever ran last.
+		[ -n "${KLP_TEST_REPORTED:-}" ] && [ -z "${KLP_TEST_FAILED:-}" ] && {
+			rm -rf "$workdir"
+			return 0
+		}
+		# run on its own there is no runner to say where it was kept
+		[ -n "${KLP_TEST_WORKDIR:-}" ] ||
+			echo "# kept $workdir"
+		;;
+	esac
 }
 
 # setup [exported symbol...]
@@ -213,7 +253,7 @@ setup()
 	else
 		workdir="$(mktemp -d)" || fail "mktemp failed"
 	fi
-	trap cleanup EXIT
+	trap 'cleanup exit' EXIT
 
 	export_syms "$@"
 }
diff --git a/tools/objtool/tests/run-tests.sh b/tools/objtool/tests/run-tests.sh
index e6f1ac1b5d0d..c0762f532d2a 100755
--- a/tools/objtool/tests/run-tests.sh
+++ b/tools/objtool/tests/run-tests.sh
@@ -31,13 +31,15 @@ A test may be named with or without its "test-" prefix and ".sh" suffix, and is
 looked for in both directories.
 
 Options:
-    -k, --keep    do not delete each test's working directory; print its path,
-                  so the objects a failing test built can be looked at
+    -k, --keep    same as KEEP=all (see below)
 
 Environment:
     OBJTOOL       objtool binary to test (default ../objtool)
     CC            compiler used to build fixtures (default gcc)
     ARCH          architecture the tests are for (default: uname -m)
+    KEEP          failed  keep only failing tests (default)
+                  all     keep every test's working directory
+                  none    remove all working directories
 
 A test which needs something of its own says so in its skip message.
 EOF
@@ -46,16 +48,28 @@ EOF
 
 cd "$(dirname "$0")" || exit 1
 
+keep_from_args=
 while [ $# -gt 0 ]; do
 	case "$1" in
 	-h|--help)	usage ;;
-	-k|--keep)	export KLP_TEST_KEEP=1; shift ;;
+	-k|--keep)	keep_from_args=all; shift ;;
 	--)		shift; break ;;
 	-*)		echo "unknown option: $1" >&2; usage 1 ;;
 	*)		break ;;
 	esac
 done
 
+KLP_TEST_KEEP="${KEEP:-failed}"
+[ -n "$keep_from_args" ] && KLP_TEST_KEEP="$keep_from_args"
+case "$KLP_TEST_KEEP" in
+all|none|failed) ;;
+*)
+	echo "invalid KEEP=$KLP_TEST_KEEP (want failed, all, or none)" >&2
+	exit 1
+	;;
+esac
+export KLP_TEST_KEEP
+
 echo "TAP version 13"
 
 # Sourcing the harness runs its preflight, which decides which architecture
@@ -109,9 +123,33 @@ fi
 rundir="$(mktemp -d "${TMPDIR:-/tmp}/klp-tests.XXXXXXXX")" ||
 	{ echo "Bail out! cannot create a working directory" >&2; exit 1; }
 
+# The run's own two levels: each test's directory, and the one per source
+# directory holding them.  Take them away if the tests left them empty, and
+# say so if they did not.  Either rmdir may fail -- the glob stays unexpanded
+# when nothing was created -- so ask the directory itself rather than trusting
+# the status.  Never rm -rf: what to keep is the tests' decision, made in
+# cleanup() as each one exits, and this must not overrule it.
+reap_rundir()
+{
+	rmdir "$rundir"/*/ 2>/dev/null
+	rmdir "$rundir" 2>/dev/null
+	[ -d "$rundir" ]
+}
+
+# An interrupted run has the same directory to answer for, and the tests it
+# never reached will not clean up on their way out.  The one it was running
+# has, and under the default it kept what it had built, so say where.
+interrupted()
+{
+	reap_rundir && echo "# interrupted; what was built is in $rundir"
+	exit 130
+}
+trap interrupted INT TERM HUP
+
 echo "1..${#tests[@]}"
 
 pass=0 fail=0 static_skip=0 probe_skip=0 xfail=0 xpass=0
+failed_dirs=()
 
 for t in "${tests[@]}"; do
 	out="$(KLP_TEST_WORKDIR="$rundir/${t%.sh}" ./"$t" 2>&1)"
@@ -147,16 +185,16 @@ for t in "${tests[@]}"; do
 		rest="$rest${rest:+$'\n'}was: $result"
 		result="not ok - $(basename "$t" .sh): undeclared skip"
 		result="$result (use gcc_only/clang_only or require_input_*)"
-		fail=$((fail + 1)) ;;
+		fail=$((fail + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;;
 	"not ok"*"# TODO"*)	xfail=$((xfail + 1)) ;;
-	"ok"*"# TODO"*)		xpass=$((xpass + 1)) ;;
-	"not ok"*)		fail=$((fail + 1)) ;;
+	"ok"*"# TODO"*)		xpass=$((xpass + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;;
+	"not ok"*)		fail=$((fail + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;;
 	"ok"*)			pass=$((pass + 1)) ;;
 	*)
 		# No result line at all: the test died before reporting.
 		rest="$rest${rest:+$'\n'}exited $rc without a result line"
 		result="not ok - $(basename "$t" .sh): no TAP result"
-		fail=$((fail + 1)) ;;
+		fail=$((fail + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;;
 	esac
 
 	echo "$result"
@@ -167,15 +205,35 @@ done
 echo "# pass:$pass fail:$fail static-skip:$static_skip" \
      "probe-skip:$probe_skip xfail:$xfail xpass:$xpass"
 
-# A failure is the one time the objects matter, and by default they are
-# already gone.  Say so then rather than in the usage text nobody reads while
-# something is broken.
-if [ -n "${KLP_TEST_KEEP:-}" ]; then
-	echo "# working directories kept in $rundir -- inspect, then rm -rf it"
-elif ! rmdir "$rundir"/*/ "$rundir" 2>/dev/null; then
-	echo "# $rundir was not empty; a test did not clean up after itself"
-elif [ "$fail" != 0 ] || [ "$xpass" != 0 ]; then
-	echo "# re-run with --keep to hold on to what a failing test built"
-fi
+case "$KLP_TEST_KEEP" in
+all)
+	echo "# keep=all: workdirs kept in $rundir"
+	echo "# inspect: diff.log, readelf -S out.o under each test-* subdirectory"
+	echo "# cleanup: rm -rf $rundir"
+	;;
+failed)
+	if [ "${#failed_dirs[@]}" -gt 0 ]; then
+		echo "# keep=failed: ${#failed_dirs[@]} failing test(s) kept under $rundir:"
+		for d in "${failed_dirs[@]}"; do
+			echo "#   ${d#"$rundir"/}/"
+		done
+		echo "# inspect: diff.log  readelf -S out.o"
+		echo "# one test: $PWD/run-tests.sh <name>"
+		echo "# cleanup: rm -rf $rundir"
+	elif reap_rundir; then
+		echo "# $rundir was not empty;" \
+		     "a test did not clean up after itself"
+	fi
+	;;
+none)
+	if reap_rundir; then
+		echo "# $rundir was not empty;" \
+		     "a test did not clean up after itself"
+	elif [ "$fail" != 0 ] || [ "$xpass" != 0 ]; then
+		echo "# keep=none: artifacts were removed" \
+		     "(re-run with KEEP=failed or KEEP=all)"
+	fi
+	;;
+esac
 
 [ "$fail" = 0 ] && [ "$xpass" = 0 ]
-- 
2.53.0-Meta


  parent reply	other threads:[~2026-09-15 23:35 UTC|newest]

Thread overview: 69+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-15 23:33 [PATCH v4 00/58] Unit test framework for klp-build toolchain Song Liu
2026-09-15 23:33 ` [PATCH v4 01/58] objtool: Add test harness for the klp subcommands Song Liu
2026-09-15 23:33 ` [PATCH v4 02/58] objtool/klp: Check the klp test environment once, before any test Song Liu
2026-09-15 23:33 ` [PATCH v4 03/58] objtool/klp: Group the klp tests by architecture Song Liu
2026-09-15 23:34 ` [PATCH v4 04/58] objtool/klp: Classify klp test outcomes Song Liu
2026-09-15 23:34 ` [PATCH v4 05/58] objtool/klp: Build klp test fixtures through the harness Song Liu
2026-09-15 23:34 ` [PATCH v4 06/58] objtool/klp: Grow the klp test harness vocabulary Song Liu
2026-09-15 23:50   ` sashiko-bot
2026-09-16 17:33     ` Song Liu
2026-09-15 23:34 ` [PATCH v4 07/58] objtool/klp: Give each run one working directory, one per test inside it Song Liu
2026-09-15 23:34 ` [PATCH v4 08/58] objtool/klp: Run the klp tests under set -u Song Liu
2026-09-15 23:34 ` [PATCH v4 09/58] objtool/klp: Document the klp test harness Song Liu
2026-09-15 23:34 ` Song Liu [this message]
2026-09-15 23:34 ` [PATCH v4 11/58] objtool: Forward toolchain variables to the klp test runner Song Liu
2026-09-15 23:34 ` [PATCH v4 12/58] objtool/klp: Add test for rejecting changed data Song Liu
2026-09-15 23:34 ` [PATCH v4 13/58] objtool/klp: Add test for newly introduced data Song Liu
2026-09-15 23:34 ` [PATCH v4 14/58] objtool/klp: Add test for newly introduced functions Song Liu
2026-09-15 23:34 ` [PATCH v4 15/58] objtool/klp: Add test for static local correlation Song Liu
2026-09-15 23:46   ` sashiko-bot
2026-09-16 17:45     ` Song Liu
2026-09-15 23:34 ` [PATCH v4 16/58] objtool/klp: Add test for cold function halves Song Liu
2026-09-15 23:34 ` [PATCH v4 17/58] objtool/klp: Add test for special section extraction Song Liu
2026-09-15 23:34 ` [PATCH v4 18/58] objtool/klp: Add test for selective " Song Liu
2026-09-15 23:34 ` [PATCH v4 19/58] objtool/klp: Add test for jump table key relocations Song Liu
2026-09-15 23:34 ` [PATCH v4 20/58] objtool/klp: Add test for rejecting module-owned static branch keys Song Liu
2026-09-15 23:34 ` [PATCH v4 21/58] objtool/klp: Add test for rejecting module-owned static call keys Song Liu
2026-09-15 23:34 ` [PATCH v4 22/58] objtool/klp: Add test for symids in discarded sections Song Liu
2026-09-15 23:34 ` [PATCH v4 23/58] objtool/klp: Add test for rejecting references to init code/data Song Liu
2026-09-15 23:34 ` [PATCH v4 24/58] objtool/klp: Add test for correlation across ThinLTO name mangling Song Liu
2026-09-15 23:34 ` [PATCH v4 25/58] objtool/klp: Add test for objects without .modinfo Song Liu
2026-09-15 23:34 ` [PATCH v4 26/58] objtool/klp: Add test for unchecksummed input Song Liu
2026-09-15 23:34 ` [PATCH v4 27/58] objtool/klp: Add klp diff and post-link regression tests Song Liu
2026-09-15 23:34 ` [PATCH v4 28/58] objtool/klp: Add test for klp reloc section naming in module objects Song Liu
2026-09-15 23:34 ` [PATCH v4 29/58] objtool/klp: Add test for vmlinux relocs in a patched module Song Liu
2026-09-15 23:34 ` [PATCH v4 30/58] objtool/klp: Add test for Module.symvers path normalization Song Liu
2026-09-15 23:34 ` [PATCH v4 31/58] objtool/klp: Add test for the contents of the klp_funcs list Song Liu
2026-09-15 23:34 ` [PATCH v4 32/58] objtool/klp: Add test for EXPORT_SYMBOL_FOR_MODULES references Song Liu
2026-09-15 23:34 ` [PATCH v4 33/58] objtool/klp: Add test for new references to exported symbols Song Liu
2026-09-15 23:48   ` sashiko-bot
2026-09-16 17:40     ` Song Liu
2026-09-15 23:34 ` [PATCH v4 34/58] objtool/klp: Add test for empty x86 alternative replacements Song Liu
2026-09-15 23:34 ` [PATCH v4 35/58] objtool/klp: Add test for recorded checksum values Song Liu
2026-09-15 23:34 ` [PATCH v4 36/58] objtool/klp: Add test for position-independent checksums Song Liu
2026-09-15 23:34 ` [PATCH v4 37/58] objtool/klp: Add test for sympos in module objects Song Liu
2026-09-15 23:34 ` [PATCH v4 38/58] objtool/klp: Add test for sympos resolved against a linked vmlinux Song Liu
2026-09-15 23:34 ` [PATCH v4 39/58] objtool/klp: Add test for static locals which must not be correlated Song Liu
2026-09-15 23:51   ` sashiko-bot
2026-09-16 17:51     ` Song Liu
2026-09-15 23:34 ` [PATCH v4 40/58] objtool/klp: Add test for __bug_table, __ex_table and __mcount_loc extraction Song Liu
2026-09-15 23:34 ` [PATCH v4 41/58] objtool/klp: Add test for kCFI prefix symbols and traps Song Liu
2026-09-15 23:34 ` [PATCH v4 42/58] objtool/klp: Add test for symbols whose linkage the patch changes Song Liu
2026-09-15 23:34 ` [PATCH v4 43/58] objtool/klp: Test rejection of a file-local static branch key Song Liu
2026-09-15 23:34 ` [PATCH v4 44/58] objtool/klp: Test a hand-built livepatch module's static call keys Song Liu
2026-09-15 23:34 ` [PATCH v4 45/58] objtool/klp: Test text annotations on alternative replacements Song Liu
2026-09-15 23:34 ` [PATCH v4 46/58] objtool/klp: Add test for data object checksums Song Liu
2026-09-15 23:53   ` sashiko-bot
2026-09-16  7:00     ` Song Liu
2026-09-15 23:34 ` [PATCH v4 47/58] objtool/klp: Add test for symbols with no checksum entry of their own Song Liu
2026-09-15 23:34 ` [PATCH v4 48/58] objtool/klp: Add test for a static branch introduced by the patch Song Liu
2026-09-15 23:34 ` [PATCH v4 49/58] objtool/klp: Add test for tracepoint and pr_debug static branch keys Song Liu
2026-09-15 23:34 ` [PATCH v4 50/58] objtool/klp: Add test for a static call introduced by the patch Song Liu
2026-09-15 23:34 ` [PATCH v4 51/58] objtool/klp: Add test for instruction operand checksums Song Liu
2026-09-15 23:34 ` [PATCH v4 52/58] objtool/klp: Add test for alternative replacement code in checksums Song Liu
2026-09-15 23:34 ` [PATCH v4 53/58] objtool/klp: Add test for the alignment of cloned data sections Song Liu
2026-09-15 23:34 ` [PATCH v4 54/58] objtool/klp: Add test for a patch which strips a data annotation Song Liu
2026-09-15 23:34 ` [PATCH v4 55/58] objtool/klp: Add test for absolute and __ADDRESSABLE symbols Song Liu
2026-09-15 23:34 ` [PATCH v4 56/58] objtool/klp: Add test for UBSAN metadata in an unchanged function Song Liu
2026-09-15 23:34 ` [PATCH v4 57/58] objtool/klp: Add test for Clang switch jump tables Song Liu
2026-09-15 23:34 ` [PATCH v4 58/58] objtool/klp: Add test for ThinLTO symbols sharing a demangled name Song Liu

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260915233454.2405019-11-song@kernel.org \
    --to=song@kernel.org \
    --cc=jikos@kernel.org \
    --cc=joe.lawrence@redhat.com \
    --cc=jpoimboe@kernel.org \
    --cc=kernel-team@meta.com \
    --cc=live-patching@vger.kernel.org \
    --cc=mbenes@suse.cz \
    --cc=peterz@infradead.org \
    --cc=pmladek@suse.com \
    --cc=puranjay@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox