aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Borkmann <daniel@iogearbox.net>2019-09-26 13:47:10 +0200
committerDaniel Borkmann <daniel@iogearbox.net>2019-09-27 15:22:43 +0200
commit2f1dd11d0268cbd32a0b90637bf528e96c77fb1f (patch)
tree7bc4475400660e716d5de6c12e5647e3a044341d
downloadpw-2f1dd11d0268cbd32a0b90637bf528e96c77fb1f.tar.gz
pw: initial import of pw tools
There it is. See README for more details and setup. Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
-rw-r--r--.gitconfig38
-rw-r--r--.l2mdconfig14
-rw-r--r--.mb2q.yaml16
-rw-r--r--Makefile8
-rw-r--r--README181
-rwxr-xr-xpw-apply70
-rwxr-xr-xpw-backport47
-rwxr-xr-xpw-check221
-rwxr-xr-xpw-pull42
9 files changed, 637 insertions, 0 deletions
diff --git a/.gitconfig b/.gitconfig
new file mode 100644
index 0000000..f02f45a
--- /dev/null
+++ b/.gitconfig
@@ -0,0 +1,38 @@
+[user]
+ name = Daniel Borkmann
+ email = daniel@iogearbox.net
+
+[sendmail]
+ smtpserver = smtp.iogearbox.net
+ suppresscc = all
+ chainreplyto = false
+ suppressfrom = true
+
+[color]
+ ui = auto
+
+[diff "default"]
+ xfuncname = "^[[:alpha:]$_].*[^:]$"
+
+[credential]
+ helper = cache
+
+[diff]
+ renames = false
+
+[format]
+ numbered = auto
+ thread = shallow
+
+[log]
+ mailmap = true
+
+[url "ssh://git@gitolite.kernel.org"]
+ insteadOf = https://git.kernel.org
+ insteadOf = http://git.kernel.org
+ insteadOf = git://git.kernel.org
+
+[pw]
+ server = https://patchwork.ozlabs.org/
+ project = netdev
+ token = xyz
diff --git a/.l2mdconfig b/.l2mdconfig
new file mode 100644
index 0000000..e747c05
--- /dev/null
+++ b/.l2mdconfig
@@ -0,0 +1,14 @@
+[general]
+ maildir = ~/.l2md/maildir/common
+ period = 30
+
+[repo bpf]
+ url = https://lore.kernel.org/bpf/0
+ maildir = ~/.l2md/maildir/bpf
+ initial_import = 10000
+
+[repo netdev]
+ url = https://lore.kernel.org/netdev/1
+ url = https://lore.kernel.org/netdev/0
+ maildir = ~/.l2md/maildir/netdev
+ initial_import = 10000
diff --git a/.mb2q.yaml b/.mb2q.yaml
new file mode 100644
index 0000000..6bb4a60
--- /dev/null
+++ b/.mb2q.yaml
@@ -0,0 +1,16 @@
+committer: Daniel Borkmann <daniel@iogearbox.net>
+
+nocc_addrs:
+ - daniel@iogearbox.net
+
+list_addrs:
+ - bpf@vger.kernel.org
+
+drop_from:
+ - '<MAILER-DAEMON@'
+
+link_base: https://lore.kernel.org/bpf/
+
+dropcc: False
+
+sob_before_cc: True
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8958d8f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,8 @@
+install:
+ cp pw-apply /usr/bin/pw-apply
+ cp pw-check /usr/bin/pw-check
+ cp pw-pull /usr/bin/pw-pull
+ cp pw-backport /usr/bin/pw-backport
+
+uninstall:
+ $(RM) /usr/bin/pw-apply /usr/bin/pw-pull /usr/bin/pw-check /usr/bin/pw-backport
diff --git a/README b/README
new file mode 100644
index 0000000..bb96e42
--- /dev/null
+++ b/README
@@ -0,0 +1,181 @@
+Kernel patch/review workflow
+============================
+
+Minimal howto for the tools involved in my daily patch handling and kernel
+mailing list interaction. It may be not perfect and the workflow as well
+as tooling/scripts can be improved for sure, but I thought I'd document it
+here for myself or others in case it's found useful. The example config
+is very much bpf and netdev centric and needs to be adapted for other
+subsystems.
+
+Please send patches to Daniel Borkmann <daniel@iogearbox.net>.
+
+Used tooling:
+-------------
+
+I mainly use git, git-pw, git-send-email, l2md, pw-*, quilttools, msmtp,
+mutt and patchwork UI for all my daily list workflow. The command-line
+tools themselves can be obtained via:
+
+ - git, git-send-email, msmtp, mutt (available as distro packages)
+ - git://git.kernel.org/pub/scm/linux/kernel/git/tglx/quilttools.git
+ - git://git.kernel.org/pub/scm/linux/kernel/git/dborkman/l2md.git
+ - git://git.kernel.org/pub/scm/linux/kernel/git/dborkman/pw.git
+ - https://github.com/getpatchwork/git-pw
+
+Base setup:
+-----------
+
+Example configs for various tools are in this repository as well.
+
+1. git-pw:
+~~~~~~~~~~
+
+Configuration of git-pw is straight forward, simply follow the below
+steps. The authentication token can be generated from the patchwork
+UI from https://patchwork.ozlabs.org/user/. Below example is related
+to netdev:
+
+ $ git config --global pw.server 'https://patchwork.ozlabs.org/'
+ $ git config --global pw.project 'netdev'
+ $ git config --global pw.token 'API-TOKEN'
+
+2. quilttools aka mb2q:
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Example .mb2q.yaml in this repo, needs to be in home directory. More
+information on the options can be found in the quilttools repository.
+
+quilttools as well as git-pw is needed for the pw-apply tool in this
+repository. Although I don't use quilt, I really like mb2q's patch
+normalization.
+
+3. l2md:
+~~~~~~~~
+
+Example .l2mdconfig for lists I follow in this repo, needs to be in home
+directory. The l2md repository also has additional information with
+regards to mutt integration and a service unit file for letting it run
+in the background automatically. I'm using l2md for fetching new emails
+and msmtp for sending them. My l2md feeds into mutt, and msmtp is used
+for mutt as well as git-send-email.
+
+Patch handling:
+---------------
+
+Main tools are pw-apply and pw-pull, both minimal, quick and dirty scripts
+to get the job done. pw-check is called from pw-apply. pw-backport for the
+backports to the stable tree. Patches for improving them are very welcome.
+
+1. Initial patch triage:
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Patches from netdev and bpf lists land under netdev patchwork project, hence
+first manual step is to delegate them accordingly such that they land in the
+delegate's todo:
+
+ https://patchwork.ozlabs.org/project/netdev/list/
+
+Todo list:
+
+ https://patchwork.ozlabs.org/user/todo/netdev/
+
+From public view, the current BPF todo list is accessible via:
+
+ https://patchwork.ozlabs.org/project/netdev/list/?series=&submitter=&state=&q=&archive=&delegate=77147
+
+All delegation and setting patches into 'Changes requested' is done through
+the web UI. This remains to be improved for more automation.
+
+2. Applying patches:
+~~~~~~~~~~~~~~~~~~~~
+
+Applying patches from patchwork, add Tested-by, and Acked-by from others.
+Patches are always identified by the series id even if it's just a single
+patch. Use -t/-a to propagate tags from the cover letter to individual
+patches, multiple people separated by comma:
+
+ $ pw-apply -s 132626 -- -t 'Joe Hacker <j@hack.er>' -a 'Joe Hacker <jh@ack.er>, Acker Jon <ack@er.jon>'
+
+The pw-apply tool can also handle mboxes directly compressed in gzip or
+uncompressed format:
+
+ $ pw-apply -m https://patchwork.ozlabs.org/series/132626/mbox/ -- -t 'Joe Hacker <j@hack.er>'
+
+Another example without adding optional tags, this time from lore:
+
+ $ pw-apply -m https://lore.kernel.org/bpf/20190925203745.3173184-1-andriin@fb.com/t.mbox.gz
+
+The pw-apply tool normalizes the patches through mb2q, sorts and groups
+tags, adds permanent links to the lore list archive and merges them into
+the master branch with the maintainer's SOB added to the chain.
+
+After they have been applied and sanity checked successfully, mark patches
+as accepted in patchwork. The -a before the optional "--" will tell pw-apply
+to mark the series as accepted, it does _not_ re-apply the series or do
+anything else:
+
+ $ pw-apply -s 132626 -a
+
+Note that this does not work in combination with the -m parameter since
+the stand-alone mailbox does not have any context of patchwork.
+
+Full workflow:
+
+ $ pw-apply -s 132626
+ # Double check if all looks good in the git log
+ $ pw-apply -s 132626 -a
+
+Currently, the acceptance email must be sent manually, but this will be
+automated in near term and integrated into -a.
+
+Side-note, manual sanity checking:
+
+ $ pw-check -s e3439af4a339acd7fddbd6d59b8ecefaac07a611 -e d21b06927c8bf8083c64a95f272bc5491682ae24
+ Commit: d21b06927c8b ("bpf: Fix bpf_event_output re-entry issue")
+ Fixes tag: Fixes: ae5a3a828cd0 ("bpf: add perf event notificaton support for sock_ops")
+ Has these problem(s):
+ - Target SHA1 does not exist
+
+Note that pw-check is called automatically for all applied patches through
+pw-apply, so above example output is just to demonstrate a manual workflow.
+
+3. Pull requests:
+~~~~~~~~~~~~~~~~~
+
+Pull requests together with a consistent format are created as described in
+below example. I do like consistency, hence even the pre-cooked template.
+The commit sha is the merge base, meaning the last commit that both trees
+had before patches have been applied. This could be automated but my own
+preference is to explicitly state it here as both {bpf,bpf-next} trees don't
+have too many commits piling up. Again, this workflow is mostly bpf specific
+and the scripts would need to be adapted for other subsystems.
+
+ $ pw-pull -t bpf -s aef70a1f44c0b570e6345c02c2d240471859f0a4
+ PR: pr-bpf-2019-09-26.patch
+
+Currently the -t option only accepts: bpf, bpf-next
+
+The resulting file is then edited to describe the changes that the pull-request
+contains. It's then sent out same way as other patches through git-send-email:
+
+ $ git send-email --to davem@davemloft.net --cc jakub.kicinski@netronome.com --cc daniel@iogearbox.net --cc ast@kernel.org --cc netdev@vger.kernel.org --cc bpf@vger.kernel.org pr-bpf-2019-09-26.patch
+
+The PR will then show up the usual way in patchwork and assigned to the higher
+level maintainer delegate for pulling.
+
+4. Backporting to stable:
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Backporting is done through the pw-backport tool which automatically adds the
+SOB and upstream commit sha to the commit message.
+
+It's tailored to https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/.
+In the local git clone, the following workflow can be used:
+
+ $ git pull origin master
+ $ git checkout -b linux-5.3.y origin/linux-5.3.y
+ $ pw-backport d895a0f16fadb26d22ab531c49768f7642ae5c3e
+ Applying: d895a0f16fad bpf: fix accessing bpf_sysctl.file_pos on s390
+
+The pw-backport can also take multiple shas as command line arguments.
diff --git a/pw-apply b/pw-apply
new file mode 100755
index 0000000..b8b9dcf
--- /dev/null
+++ b/pw-apply
@@ -0,0 +1,70 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2019 Daniel Borkmann <daniel@iogearbox.net>
+
+usage()
+{
+ cat <<-EOF
+ usage: pw-apply [-h] [-s SERIES] [-m MBOX] [-a] -- [-C]
+ [-a ACKEDBY] [-r REVIEWEDBY] [-t TESTEDBY]
+EOF
+ exit
+}
+
+mbox_from_url()
+{
+ curl $1 | gunzip -f -c > mbox.i
+}
+
+mbox_from_series()
+{
+ git pw series show $1 -f simple 2> /dev/null | \
+ sed ''/Complete/s//$(printf "\033[1mComplete\033[0m")/''
+ git pw series download $1 mbox.i 2> /dev/null
+}
+
+accept_series()
+{
+ for patch in $(git pw series show $1 -f simple 2> /dev/null | \
+ sed -n '/^Patches/,$p' | \
+ sed -r 's/.* ([0-9]*) .*/\1/g')
+ do
+ echo "Accepting $patch:"
+ git pw patch update --state accepted $patch -f simple 2> /dev/null
+ done
+ echo "Done, all accepted!"
+ rm -f mbox.i
+ exit
+}
+
+branch="mbox"
+series=""
+accept=""
+mbox=""
+head_old=$(git rev-parse --verify HEAD)
+while true; do
+ case "$1" in
+ -s | --series ) series="$2"; shift 2 ;;
+ -a | --accept ) accept="1"; shift ;;
+ -m | --mbox ) mbox="$2"; shift 2 ;;
+ -h | --help ) usage; break ;;
+ -- ) shift; break ;;
+ * ) break ;;
+ esac
+done
+[ ! -z "$mbox" ] && [ ! -z "$series" ] && usage
+[ -z "$mbox" ] && [ -z "$series" ] && usage
+[ ! -z "$accept" ] && [ ! -z "$mbox" ] && usage
+[ ! -z "$series" ] && mbox_from_series $series
+[ ! -z "$mbox" ] && mbox_from_url $mbox
+[ ! -z "$accept" ] && accept_series $series
+git checkout -b $branch
+mb2q --mboxout mbox.o "$@" mbox.i
+git am -3 mbox.o
+git checkout master
+git merge --stat --ff $branch
+git branch -d $branch
+rm -f mbox.i mbox.o
+head_new=$(git rev-parse --verify HEAD)
+pw-check -s $head_old -e $head_new
diff --git a/pw-backport b/pw-backport
new file mode 100755
index 0000000..59a959d
--- /dev/null
+++ b/pw-backport
@@ -0,0 +1,47 @@
+#!/bin/bash
+# SPDX-License-Identifier: Apache-2.0
+#
+# Copyright (C) 2019 Daniel Borkmann <daniel@iogearbox.net>
+# Copyright (C) 2019 Authors of Cilium
+
+cherry_pick()
+{
+ REM="origin/master"
+ CID=$1
+ BRANCHES=`git branch -q -r --contains $CID $REM 2> /dev/null`
+ if ! echo ${BRANCHES} | grep -q ".*master.*"; then
+ echo "Commit $CID not in $REM!"
+ exit 1
+ fi
+ TMPF=`mktemp cp.XXXXXX`
+ FROM=`git show --pretty=email $CID | head -n 2 | grep "From: "`
+ FULL_ID=`git show $CID | head -n 1 | cut -f 2 -d ' '`
+ git format-patch -1 $FULL_ID --stdout | sed '/^$/Q' > $TMPF
+ echo "" >> $TMPF
+ echo "[ upstream commit $FULL_ID ]" >> $TMPF
+ git format-patch -1 $FULL_ID --stdout | sed -n '/^$/,$p' >> $TMPF
+ echo "Applying: $(git log -1 --oneline $FULL_ID)"
+ git am --quiet -3 --signoff $TMPF
+ rm $TMPF
+}
+
+main()
+{
+ for CID in "$@"; do
+ cherry_pick "$CID"
+ done
+}
+
+usage()
+{
+ cat <<-EOF
+ usage: pw-backport <commit-id> [commit-id ...]
+EOF
+ exit
+}
+
+if [ $# -lt 1 ]; then
+ usage
+fi
+
+main "$@"
diff --git a/pw-check b/pw-check
new file mode 100755
index 0000000..0eb8c97
--- /dev/null
+++ b/pw-check
@@ -0,0 +1,221 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Major parts from verify_fixes.sh and verify_signedoff.sh by
+# Stephen and Greg. Only integrated the checks into pw-check.
+#
+# Copyright (C) 2019 Stephen Rothwell <sfr@canb.auug.org.au>
+# Copyright (C) 2019 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+# Copyright (C) 2019 Daniel Borkmann <daniel@iogearbox.net>
+
+usage()
+{
+ cat <<-EOF
+ usage: pw-check [-h] [-s START_COMMIT] [-e END_COMMIT]
+EOF
+ exit
+}
+
+split_re='^([Cc][Oo][Mm][Mm][Ii][Tt])?[[:space:]]*([[:xdigit:]]{5,})([[:space:]]*)(.*)$'
+nl=$'\n'
+tab=$'\t'
+
+strip_spaces()
+{
+ [[ "$1" =~ ^[[:space:]]*(.*[^[:space:]])[[:space:]]*$ ]]
+ echo "${BASH_REMATCH[1]}"
+}
+
+verify_fixes()
+{
+ git_range=$1
+ error=0
+ commits=$(git rev-list --no-merges -i --grep='^[[:space:]]*Fixes:' "${git_range}")
+ if [ -z "$commits" ]; then
+ return 0
+ fi
+
+ for c in $commits; do
+ commit_log=$(git log -1 --format='%h ("%s")' "$c")
+ commit_msg="Commit: $commit_log
+"
+ fixes_lines=$(git log -1 --format='%B' "$c" |
+ grep -i '^[[:space:]]*Fixes:')
+
+ while read -r fline; do
+ [[ "$fline" =~ ^[[:space:]]*[Ff][Ii][Xx][Ee][Ss]:[[:space:]]*(.*)$ ]]
+ f="${BASH_REMATCH[1]}"
+ fixes_msg=" Fixes tag: $fline
+ Has these problem(s):
+"
+ sha=
+ subject=
+ msg=
+ if [[ "$f" =~ $split_re ]]; then
+ first="${BASH_REMATCH[1]}"
+ sha="${BASH_REMATCH[2]}"
+ spaces="${BASH_REMATCH[3]}"
+ subject="${BASH_REMATCH[4]}"
+ if [ "$first" ]; then
+ msg="${msg:+${msg}${nl}}${tab}${tab}- leading word '$first' unexpected"
+ fi
+ if [ -z "$subject" ]; then
+ msg="${msg:+${msg}${nl}}${tab}${tab}- missing subject"
+ elif [ -z "$spaces" ]; then
+ msg="${msg:+${msg}${nl}}${tab}${tab}- missing space between the SHA1 and the subject"
+ fi
+ else
+ printf '%s%s\t\t- %s\n' "$commit_msg" "$fixes_msg" 'No SHA1 recognised'
+ commit_msg=''
+ error=1
+ continue
+ fi
+ if ! git rev-parse -q --verify "$sha" >/dev/null; then
+ printf '%s%s\t\t- %s\n' "$commit_msg" "$fixes_msg" 'Target SHA1 does not exist'
+ commit_msg=''
+ error=1
+ continue
+ fi
+
+ if [ "${#sha}" -lt 12 ]; then
+ msg="${msg:+${msg}${nl}}${tab}${tab}- SHA1 should be at least 12 digits long${nl}${tab}${tab} Can be fixed by setting core.abbrev to 12 (or more) or (for git v2.11${nl}${tab}${tab} or later) just making sure it is not set (or set to \"auto\")."
+ fi
+ # reduce the subject to the part between () if there
+ if [[ "$subject" =~ ^\((.*)\) ]]; then
+ subject="${BASH_REMATCH[1]}"
+ elif [[ "$subject" =~ ^\((.*) ]]; then
+ subject="${BASH_REMATCH[1]}"
+ msg="${msg:+${msg}${nl}}${tab}${tab}- Subject has leading but no trailing parentheses"
+ fi
+
+ # strip matching quotes at the start and end of the subject
+ # the unicode characters in the classes are
+ # U+201C LEFT DOUBLE QUOTATION MARK
+ # U+201D RIGHT DOUBLE QUOTATION MARK
+ # U+2018 LEFT SINGLE QUOTATION MARK
+ # U+2019 RIGHT SINGLE QUOTATION MARK
+ re1=$'^[\"\u201C](.*)[\"\u201D]$'
+ re2=$'^[\'\u2018](.*)[\'\u2019]$'
+ re3=$'^[\"\'\u201C\u2018](.*)$'
+
+ if [[ "$subject" =~ $re1 ]]; then
+ subject="${BASH_REMATCH[1]}"
+ elif [[ "$subject" =~ $re2 ]]; then
+ subject="${BASH_REMATCH[1]}"
+ elif [[ "$subject" =~ $re3 ]]; then
+ subject="${BASH_REMATCH[1]}"
+ msg="${msg:+${msg}${nl}}${tab}${tab}- Subject has leading but no trailing quotes"
+ fi
+
+ subject=$(strip_spaces "$subject")
+
+ target_subject=$(git log -1 --format='%s' "$sha")
+ target_subject=$(strip_spaces "$target_subject")
+
+ # match with ellipses
+ case "$subject" in
+ *...) subject="${subject%...}"
+ target_subject="${target_subject:0:${#subject}}"
+ ;;
+ ...*) subject="${subject#...}"
+ target_subject="${target_subject: -${#subject}}"
+ ;;
+ *\ ...\ *)
+ s1="${subject% ... *}"
+ s2="${subject#* ... }"
+ subject="$s1 $s2"
+ t1="${target_subject:0:${#s1}}"
+ t2="${target_subject: -${#s2}}"
+ target_subject="$t1 $t2"
+ ;;
+ esac
+
+ subject=$(strip_spaces "$subject")
+ target_subject=$(strip_spaces "$target_subject")
+
+ if [ "$subject" != "${target_subject:0:${#subject}}" ]; then
+ msg="${msg:+${msg}${nl}}${tab}${tab}- Subject does not match target commit subject${nl}${tab}${tab} Just use${nl}${tab}${tab}${tab}git log -1 --format='Fixes: %h (\"%s\")'"
+ fi
+
+ lsha=$(git rev-parse -q --verify "$sha")
+ if [ -z "$lsha" ]; then
+ count=$(git rev-list --count "$sha".."$c")
+ if [ "$count" -eq 0 ]; then
+ msg="${msg:+${msg}${nl}}${tab}${tab}- Target is not an ancestor of this commit"
+ fi
+ fi
+
+ if [ "$msg" ]; then
+ printf '%s%s%s\n' "$commit_msg" "$fixes_msg" "$msg"
+ commit_msg=''
+ error=1
+ fi
+ done <<< "$fixes_lines"
+ done
+
+ if [ ${error} -eq 1 ] ; then
+ exit 1
+ fi
+}
+
+verify_signedoff()
+{
+ git_range=$1
+ error=false
+ for c in $(git rev-list --no-merges "${git_range}"); do
+ ae=$(git log -1 --format='%ae' "$c")
+ aE=$(git log -1 --format='%aE' "$c")
+ an=$(git log -1 --format='%an' "$c")
+ aN=$(git log -1 --format='%aN' "$c")
+ ce=$(git log -1 --format='%ce' "$c")
+ cE=$(git log -1 --format='%cE' "$c")
+ cn=$(git log -1 --format='%cn' "$c")
+ cN=$(git log -1 --format='%cN' "$c")
+ sob=$(git log -1 --format='%b' "$c" | grep -i '^[[:space:]]*Signed-off-by:')
+
+ am=false
+ cm=false
+ grep -i -q "<$ae>" <<<"$sob" ||
+ grep -i -q "<$aE>" <<<"$sob" ||
+ grep -i -q ":[[:space:]]*${an}[[:space:]]*<" <<<"$sob" ||
+ grep -i -q ":[[:space:]]*${aN}[[:space:]]*<" <<<"$sob" ||
+ am=true
+ grep -i -q "<$ce>" <<<"$sob" ||
+ grep -i -q "<$cE>" <<<"$sob" ||
+ grep -i -q ":[[:space:]]*${cn}[[:space:]]*<" <<<"$sob" ||
+ grep -i -q ":[[:space:]]*${cN}[[:space:]]*<" <<<"$sob" ||
+ cm=true
+
+ if "$am" || "$cm"; then
+ printf "Commit %s\n" "$(git show -s --abbrev-commit --abbrev=12 --pretty=format:"%h (\"%s\")%n" "${c}")"
+ "$am" && printf "\tauthor Signed-off-by missing\n"
+ "$cm" && printf "\tcommitter Signed-off-by missing\n"
+ printf "\tauthor email: %s\n" "$ae"
+ printf "\tcommitter email: %s\n" "$ce"
+ readarray -t s <<< "${sob}"
+ printf "\t%s\n" "${s[@]}"
+ printf "\n"
+ error=true
+ fi
+ done
+ if "$error"; then
+ echo "Errors in tree with Signed-off-by, please fix!"
+ exit 1
+ fi
+}
+
+from=""
+to=""
+while true; do
+ case "$1" in
+ -s | --start ) from="$2"; shift 2 ;;
+ -e | --end ) to="$2"; shift 2 ;;
+ -h | --help ) usage; break ;;
+ * ) break ;;
+ esac
+done
+[ -z "$from" ] && usage
+[ -z "$to" ] && usage
+verify_signedoff "$from..$to"
+verify_fixes "$from..$to"
+echo "Checked $from -> $to: OK"
diff --git a/pw-pull b/pw-pull
new file mode 100755
index 0000000..0b4be4d
--- /dev/null
+++ b/pw-pull
@@ -0,0 +1,42 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2019 Daniel Borkmann <daniel@iogearbox.net>
+
+usage()
+{
+ cat <<-EOF
+ usage: pw-pull [-h] [-t TREE] [-s SINCE_COMMIT]
+EOF
+ exit
+}
+
+remote=""
+since=""
+tree=""
+today=$(date +%Y-%m-%d)
+while true; do
+ case "$1" in
+ -s | --since ) since="$2"; shift 2 ;;
+ -t | --tree ) tree="$2"; shift 2 ;;
+ -h | --help ) usage; break ;;
+ * ) break ;;
+ esac
+done
+[ -z "$since" ] && usage
+[ -z "$tree" ] && usage
+case $tree in
+ bpf-next ) remote=(net-next git://git.kernel.org/pub/scm/linux/kernel/git/bpf/$tree.git) ;;
+ bpf ) remote=(net git://git.kernel.org/pub/scm/linux/kernel/git/bpf/$tree.git) ;;
+ * ) usage ;;
+esac
+file="pr-$tree-$today.patch"
+git request-pull $since ${remote[1]} > $file
+echo -e "Subject: pull-request: $tree $today\n\nHi David,\n\nThe following pull-request contains BPF updates for your *${remote[0]}* tree.\n\nThe main changes are:\n\n1) ..., from XYZ. Among others:\n\n - Blah blah sub item.\n\n2) ..., from ABC.\n\nPlease consider pulling these changes from:\n\n git://git.kernel.org/pub/scm/linux/kernel/git/bpf/$tree.git\n\nThanks a lot!\n\n----------------------------------------------------------------\n\n$(cat $file)" > $file
+bug=$(grep -c gitolite $file)
+if [ "$bug" -gt "0" ]; then
+ echo "PR not generated: using gitolite url, not public one!"
+ rm -f $file
+else
+ echo "PR: $file"
+fi