From 34cd15fdd9b49bfcace32385b4de40c20585b48d Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Thu, 25 Apr 2024 23:00:27 -0400 Subject: [PATCH] docs(MAINTAINERS): Standardize the `git rebase -i HEAD` todo list --- MAINTAINERS.md | 1 + scripts/gen-rebase-todo.sh | 130 +++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100755 scripts/gen-rebase-todo.sh diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 2e8042fc99c..a0ed1bff327 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -77,6 +77,7 @@ For each set of changes to include: git config rerere.enabled true ``` - [ ] Author a `rebase-todo` by selecting relevant changes from the `master` branch rebase log (see below). + - For a convenient starting point, use `scripts/gen-rebase-todo.sh $since` to get a todo list reflecting all merge-inclusive changes from $since to the current HEAD. - Comment out any commit that should be excluded from a multi-commit PR. - If a small change from an unrelated PR is needed (rider commits), either: - Outside of a label/merge section, `pick` the commit, and indicate with a comment on the previous line the origin PR of the picked commit. diff --git a/scripts/gen-rebase-todo.sh b/scripts/gen-rebase-todo.sh new file mode 100755 index 00000000000..dd84306fd7c --- /dev/null +++ b/scripts/gen-rebase-todo.sh @@ -0,0 +1,130 @@ +#! /bin/bash +set -ueo pipefail +declare -r USAGE="Usage: $0 + +Capture merge-inclusive history of the current branch since +and output it as a todo list that will recreate it on another branch +when used to overwrite the \"noop\" contents of \`git rebase -i HEAD\`. + +This is useful for cherry-picking onto a release branch. +" +usage() { + [ -n "$1" ] && printf '%s\n' "$1" + printf '%s' "$USAGE" + exit 64 +} +case "${1-}" in + --help) usage ;; + -*) usage 1>&2 "Error: unknown option \"$1\"" ;; + '') usage 1>&2 "Error: missing commit" ;; +esac + +since="$1" + +# Get an an initial todo list from `git rebase -i` by using a fake +# editor that echoes the file to standard output and then trucates it to +# ensure that no rebase actually happens. +# Then send that todo list through a transformation pipeline. +fake_editor='cat /dev/null \ +| { + # Remove any final block of instruction comments (by appending + # blanks/comments to hold space and flushing that before other lines, + # minus the line feed before the first appended content). + sed -nE '/^$|^#/ { H; d; }; H; s/.*//; x; s/^[[:cntrl:]]//; p;' +} \ +| { + # TODO: When `gh` CLI is available, use it to look up the PR for each + # `# Branch $branchName` lines and insert a reference like + cat +} \ +| { + awk ' + # Restructure branch-specific blocks: + # * Move an isolated initial `label` into the first block (and + # remove a following no-op `reset`). + # * When a block starts with `reset` + `merge -C`, move them into + # the previous block. + + NR == 1 && match($0, /^label /) { + firstBlockPrefix = $0 "\n"; + label = substr($0, RLENGTH + 1, length($0) - RLENGTH); + noopReset = "reset " label; + next; + } + firstBlockPrefix != "" && $0 == noopReset { + next; + } + /^$|^# .*[Bb]ranch .*/ { + blockHeader = blockHeader $0 "\n"; + next; + } + blockHeader != "" { + if (cmdBuf == "" && match($0, /^reset /)) { + cmdBuf = $0 "\n"; + next; + } else if (cmdBuf != "" && match($0, /^merge -C/)) { + printf "%s%s", cmdBuf, $0 "\n"; + cmdBuf = ""; + next; + } + } + { + printf "%s%s%s%s", blockHeader, firstBlockPrefix, cmdBuf, $0 "\n" + blockHeader = ""; + firstBlockPrefix = ""; + cmdBuf = ""; + } + END { + printf "%s%s%s", blockHeader, firstBlockPrefix, cmdBuf; + } + ' +} \ +| { + # Rename each label that receives `merge -C` in a block to + # "base-$branchName". + awk ' + function addLabel(label) { + if (++labels[label] == 1) return; + print "duplicate label: " label > "/dev/stderr"; + exit 1; + } + match($0, /^$|^# .*[Bb]ranch /) { + branch = substr($0, RLENGTH + 1, length($0) - RLENGTH); + } + branch != "" && match($0, /^merge -C /) && match(prev, /^reset /) { + onto = substr(prev, RLENGTH + 1, length($0) - RLENGTH); + sub(/[[:space:]].*/, "", onto); + newOnto = "base-" branch; + addLabel(newOnto); + renames[++renameCount] = onto SUBSEP newOnto; + } + /^label / { + addLabel(substr($0, RLENGTH + 1, length($0) - RLENGTH)); + } + { + buf = buf $0 "\n"; + prev = $0; + } + END { + # For "last-wins" semantics, apply renames in reverse order. + for (i = renameCount; split(renames[i], pair, SUBSEP); i--) { + j = split("label reset", rebaseCmds, " "); + for (; cmd = rebaseCmds[j]; j--) { + newBuf = ""; + seekLine = sprintf("\n%s %s", cmd, pair[1]); + while (k = index(buf, seekLine)) { + len = length(seekLine); + r = sprintf("\n%s %s", cmd, pair[2]); + if (!match(substr(buf, k + len, 1), /[ \n]/)) r = seekLine; + newBuf = newBuf substr(buf, 1, k - 1) r; + buf = substr(buf, k + len, length(buf) - (k - 1) - len); + } + buf = newBuf buf; + } + } + printf "%s", buf; + } + ' +}