FTFY: support wordwrapping commit messages
authorJohn Koleszar <jkoleszar@google.com>
Wed, 28 Mar 2012 23:41:16 +0000 (16:41 -0700)
committerJohn Koleszar <jkoleszar@google.com>
Thu, 29 Mar 2012 21:01:51 +0000 (14:01 -0700)
It's common for commit messages to be wrapped at odd places. git-gui
is often to blame. Adds support for automatically fixing up these
messages if running ftfy --amend, and adds a new option --msg-only for
fixing only the commit message.

Change-Id: Ia7ea529f8cb7395d34d9b39f1192598e9a1e315b

tools/ftfy.sh
tools/intersect-diffs.py
tools/wrap-commit-msg.py [new file with mode: 0755]

index 05315fb..de0f0ed 100755 (executable)
@@ -1,5 +1,6 @@
 #!/bin/sh
 self="$0"
+dirname_self=$(dirname "$self")
 
 usage() {
   cat <<EOF >&2
@@ -9,9 +10,13 @@ This script applies a whitespace transformation to the commit at HEAD. If no
 options are given, then the modified files are left in the working tree.
 
 Options:
+  -h, --help     Shows this message
   -n, --dry-run  Shows a diff of the changes to be made.
   --amend        Squashes the changes into the commit at HEAD
+                     This option will also reformat the commit message.
   --commit       Creates a new commit containing only the whitespace changes
+  --msg-only     Reformat the commit message only, ignore the patch itself.
+
 EOF
   rm -f ${CLEAN_FILES}
   exit 1
@@ -34,7 +39,7 @@ vpx_style() {
 
 
 apply() {
-  patch -p1 < "$1"
+  [ $INTERSECT_RESULT -ne 0 ] && patch -p1 < "$1"
 }
 
 
@@ -59,8 +64,28 @@ EOF
 }
 
 
+show_commit_msg_diff() {
+  if [ $DIFF_MSG_RESULT -ne 0 ]; then
+    log "Modified commit message:"
+    diff -u "$ORIG_COMMIT_MSG" "$NEW_COMMIT_MSG" | tail -n +3
+  fi
+}
+
+
 amend() {
-  git commit -a --amend -C HEAD
+  show_commit_msg_diff
+  if [ $DIFF_MSG_RESULT -ne 0 ] || [ $INTERSECT_RESULT -ne 0 ]; then
+    git commit -a --amend -F "$NEW_COMMIT_MSG"
+  fi
+}
+
+
+diff_msg() {
+  git log -1 --format=%B > "$ORIG_COMMIT_MSG"
+  "${dirname_self}"/wrap-commit-msg.py \
+      < "$ORIG_COMMIT_MSG" > "$NEW_COMMIT_MSG"
+  cmp -s "$ORIG_COMMIT_MSG" "$NEW_COMMIT_MSG"
+  DIFF_MSG_RESULT=$?
 }
 
 
@@ -68,7 +93,10 @@ amend() {
 ORIG_DIFF=orig.diff.$$
 MODIFIED_DIFF=modified.diff.$$
 FINAL_DIFF=final.diff.$$
+ORIG_COMMIT_MSG=orig.commit-msg.$$
+NEW_COMMIT_MSG=new.commit-msg.$$
 CLEAN_FILES="${ORIG_DIFF} ${MODIFIED_DIFF} ${FINAL_DIFF}"
+CLEAN_FILES="${CLEAN_FILES} ${ORIG_COMMIT_MSG} ${NEW_COMMIT_MSG}"
 
 # Preconditions
 [ $# -lt 2 ] || usage
@@ -95,28 +123,30 @@ done
 git diff --no-color --no-ext-diff > "${MODIFIED_DIFF}"
 
 # Intersect the two diffs
-$(dirname ${self})/intersect-diffs.py \
+"${dirname_self}"/intersect-diffs.py \
     "${ORIG_DIFF}" "${MODIFIED_DIFF}" > "${FINAL_DIFF}"
 INTERSECT_RESULT=$?
 git reset --hard >/dev/null
 
-if [ $INTERSECT_RESULT -eq 0 ]; then
-  # Handle options
-  if [ -n "$1" ]; then
-    case "$1" in
-      -h|--help) usage;;
-      -n|--dry-run) cat "${FINAL_DIFF}";;
-      --commit) apply "${FINAL_DIFF}"; commit;;
-      --amend) apply "${FINAL_DIFF}"; amend;;
-      *) usage;;
-    esac
-  else
-    apply "${FINAL_DIFF}"
-    if ! git diff --quiet; then
-      log "Formatting changes applied, verify and commit."
-      log "See also: http://www.webmproject.org/code/contribute/conventions/"
-      git diff --stat
-    fi
+# Fixup the commit message
+diff_msg
+
+# Handle options
+if [ -n "$1" ]; then
+  case "$1" in
+    -h|--help) usage;;
+    -n|--dry-run) cat "${FINAL_DIFF}"; show_commit_msg_diff;;
+    --commit) apply "${FINAL_DIFF}"; commit;;
+    --amend) apply "${FINAL_DIFF}"; amend;;
+    --msg-only) amend;;
+    *) usage;;
+  esac
+else
+  apply "${FINAL_DIFF}"
+  if ! git diff --quiet; then
+    log "Formatting changes applied, verify and commit."
+    log "See also: http://www.webmproject.org/code/contribute/conventions/"
+    git diff --stat
   fi
 fi
 
index fce2289..be9dea5 100755 (executable)
@@ -182,7 +182,6 @@ def main():
 
     if out_hunks:
         print FormatDiffHunks(out_hunks)
-    else:
         sys.exit(1)
 
 if __name__ == "__main__":
diff --git a/tools/wrap-commit-msg.py b/tools/wrap-commit-msg.py
new file mode 100755 (executable)
index 0000000..b53926a
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+##  Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+##
+##  Use of this source code is governed by a BSD-style license
+##  that can be found in the LICENSE file in the root of the source
+##  tree. An additional intellectual property rights grant can be found
+##  in the file PATENTS.  All contributing project authors may
+##  be found in the AUTHORS file in the root of the source tree.
+##
+"""Wraps paragraphs of text, preserving manual formatting
+
+This is like fold(1), but has the special convention of not modifying lines
+that start with whitespace. This allows you to intersperse blocks with
+special formatting, like code blocks, with written prose. The prose will
+be wordwrapped, and the manual formatting will be preserved.
+
+ * This won't handle the case of a bulleted (or ordered) list specially, so
+   manual wrapping must be done.
+
+Occasionally it's useful to put something with explicit formatting that
+doesn't look at all like a block of text inline.
+
+  indicator = has_leading_whitespace(line);
+  if (indicator)
+    preserve_formatting(line);
+
+The intent is that this docstring would make it through the transform
+and still be legible and presented as it is in the source. If additional
+cases are handled, update this doc to describe the effect.
+"""
+
+__author__ = "jkoleszar@google.com"
+import textwrap
+import sys
+
+def wrap(text):
+    if text:
+        return textwrap.fill(text, break_long_words=False) + '\n'
+    return ""
+
+
+def main(fileobj):
+    text = ""
+    output = ""
+    while True:
+        line = fileobj.readline()
+        if not line:
+            break
+
+        if line.lstrip() == line:
+            text += line
+        else:
+            output += wrap(text)
+            text=""
+            output += line
+    output += wrap(text)
+
+    # Replace the file or write to stdout.
+    if fileobj == sys.stdin:
+        fileobj = sys.stdout
+    else:
+        fileobj.truncate(0)
+    fileobj.write(output)
+
+if __name__ == "__main__":
+    if len(sys.argv) > 1:
+        main(open(sys.argv[1], "r+"))
+    else:
+        main(sys.stdin)