my hacky mutt-specific format-patch workflow
- From
Jeff King <peff@peff.net>
- Date
- Jan 14, 2015, 01:28 UTC
- Message-ID
- <20150114012819.GA7520@peff.net>
- In-Reply-To
- <CAGZ79kZ87ZKT5-OMTEHAMEAmZVOfku_D4vZ9jcWJ4cHrA06cSw@mail.gmail.com>
[retitling in case there is a wider audience]
On Tue, Jan 13, 2015 at 02:48:15PM -0800, Stefan Beller wrote:
> Would you mind to share (parts of) the wrapper script? We could see if that > makes sense to incorporate into format-patch as well.
Sure, but I'll warn you it's kind of gross and specific to my workflow. :)
The script (which I call mpatch) is at the end of this message. The main thing which may be of interest to other people is the cover-letter handling. I write my cover letters in my normal MUA (mutt), and then generate the series as a reply to that. The two ways it supports that are:
1. It generates the list of patches that I include in my cover letter
from the mbox. This _should_ be a "git log" one-liner, but our
--pretty placeholders do not know how to count. Ideally I could say
"[%i/%n]: %s", but neither of those first two exist (and %n would
require counting all of the commits first, which might require some
surgery to git-log). 2. Given an existing message, it will pick out the to, cc, and
message-id headers, and generate corresponding --to, --cc, and
--in-reply-to arguments to feed to format-patch. I do it hackily in
perl, but probably format-patch could do it internally if it built
on git-mailinfo.I typically have my MUA in one terminal and a shell running git in the other. Even though I could run mpatch directly from the MUA, it is often missing the context of which repo I am working in. So instead, I typically use ~/patch as a go-between for the two sessions (both for generating patch series, but also for applying with git-am). So I have this in my muttrc:
macro pager,index D '<shell-escape>rm -f $HOME/patch<enter>' macro pager,index A '<copy-message>~/patch<enter><enter>'
Applying a patch series from the list is just 'D' to clear the state, then 'A' to collect whatever patches. And then I "git am ~/patch" from the terminal window.
Generating a patch series is more like:
1. "mpatch >~/patch" from the git terminal to generate the list of
commits. No arguments necessary, because it uses @{upstream} as
the base (but occasionally I use "-3" or similar if I am sending
out new patches on top of somebody else's work). 2. Reply or start a new thread in mutt, as normal. This becomes the
cover letter (the to/cc comes from the reply, or I have mutt
aliases for the list and frequent contributors, and/or I may cut
and paste from "git log" in some cases). Mutt dumps me in vim to
write the actual message, and I ":r ~/patch" to pull it in.3. Finish and send off the cover letter. This gives it a message-id.
4. Drop the newly-sent message into ~/patch. I usually just open my
sent folder and use 'D', 'A' to copy it there. 5. "mpatch" from the git terminal. The headers are picked up from
~/patch. Sometimes I use "-v2", which is passed to format-patch
to get "[PATCH v2 i/n]".After that, I'm in mutt with an mbox full of the patches. I have this hotkey in mutt:
macro index,pager b ":set edit_headers=yes<enter><resend-message>:set edit_headers=no<enter>"
which dumps me vim, with the headers. From there I give a final proofread, write any comments below the "---", and then send it.
I imagine for the last bit many people have a similar workflow without mutt that is something like:
1. format-patch into ~/patch/*
2. $EDITOR ~/patch/*
3. git send-email ~/patch/*
Those people would potentially benefit from the format-patch in step 1 picking up the headers from an existing message.
-Peff
-- >8 -- #!/bin/sh
upstream_branch() {
current=`git symbolic-ref HEAD`
upstream=`git for-each-ref --format='%(upstream)' "$current"`
if test -n "$upstream"; then
echo $upstream
else
echo origin
fi
}get_reply_headers() {
perl -ne '
if (defined $opt && /^\s+(.*)/) {
$val .= " $1";
next;
}
if (defined $opt) {
print "--$opt=", quotemeta($val), " ";
$opt = $val = undef;
}
if (/^(cc|to):\s*(.*)/i) {
$opt = lc($1);
$val = $2;
}
elsif (/^message-id:\s*(.*)/i) {
$opt = "in-reply-to";
$val = $1;
}
'
}has_nonoption=
for i in "$@"; do
case "$i" in
-[0-9]) has_nonoption=yes ;;
-*) ;;
*) has_nonoption=yes
esac
done: ${REPLY:=$HOME/patch} test -n "$REPLY" && eval "set -- `get_reply_headers <\"$REPLY\"` \"\$@\"" test "$has_nonoption" = "yes" || set -- "$@" `upstream_branch`
git format-patch -s --stdout --from "$@" >.mbox
if test -t 1; then
mutt -f .mbox
else
perl -lne '
if (/^Subject: (.*)/) {
$subject = $1;
}
elsif ($subject && /^\s+(.*)/) {
$subject .= " $1";
}
elsif ($subject) {
print $subject;
$subject = undef;
}
' .mbox |
sed -e 's/\[PATCH /[/' \
-e 's/]/]:/' \
-e 's/^/ /'
fi
rm -f .mbox