/ util / gitconfig / commit-msg
commit-msg
  1  #!/usr/bin/env sh
  2  # Part of Gerrit Code Review (http://code.google.com/p/gerrit/)
  3  #
  4  # Licensed under the Apache License, Version 2.0 (the "License");
  5  # you may not use this file except in compliance with the License.
  6  # You may obtain a copy of the License at
  7  #
  8  # http://www.apache.org/licenses/LICENSE-2.0
  9  #
 10  # Unless required by applicable law or agreed to in writing, software
 11  # distributed under the License is distributed on an "AS IS" BASIS,
 12  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  # See the License for the specific language governing permissions and
 14  # limitations under the License.
 15  #
 16  
 17  CHANGE_ID_AFTER="Bug|Issue"
 18  MSG="$1"
 19  
 20  # Check for, and add if missing, a unique Change-Id
 21  #
 22  add_ChangeId() {
 23  	clean_message=`sed -e '
 24  		/^diff --git a\/.*/{
 25  			s///
 26  			q
 27  		}
 28  		/^Signed-off-by:/d
 29  		/^#/d
 30  	' "$MSG" | git stripspace`
 31  	if test -z "$clean_message"
 32  	then
 33  		return
 34  	fi
 35  
 36  	# Does Change-Id: already exist? if so, exit (no change).
 37  	if grep -i '^Change-Id: I[0-9a-f]\{40\}$' "$MSG" >/dev/null
 38  	then
 39  		return
 40  	fi
 41  
 42  	id=`_gen_ChangeId`
 43  	T="$MSG.tmp.$$"
 44  	AWK=awk
 45  	if [ -x /usr/xpg4/bin/awk ]; then
 46  		# Solaris AWK is just too broken
 47  		AWK=/usr/xpg4/bin/awk
 48  	fi
 49  
 50  	# How this works:
 51  	# - parse the commit message as (textLine+ blankLine*)*
 52  	# - assume textLine+ to be a footer until proven otherwise
 53  	# - exception: the first block is not footer (as it is the title)
 54  	# - read textLine+ into a variable
 55  	# - then count blankLines
 56  	# - once the next textLine appears, print textLine+ blankLine* as these
 57  	#   aren't footer
 58  	# - in END, the last textLine+ block is available for footer parsing
 59  	$AWK '
 60  	BEGIN {
 61  		# while we start with the assumption that textLine+
 62  		# is a footer, the first block is not.
 63  		isFooter = 0
 64  		footerComment = 0
 65  		blankLines = 0
 66  	}
 67  
 68  	# Skip lines starting with "#" without any spaces before it.
 69  	/^#/ { next }
 70  
 71  	# Skip the line starting with the diff command and everything after it,
 72  	# up to the end of the file, assuming it is only patch data.
 73  	# If more than one line before the diff was empty, strip all but one.
 74  	/^diff --git a/ {
 75  		blankLines = 0
 76  		while (getline) { }
 77  		next
 78  	}
 79  
 80  	# Count blank lines outside footer comments
 81  	/^$/ && (footerComment == 0) {
 82  		blankLines++
 83  		next
 84  	}
 85  
 86  	# Catch footer comment
 87  	/^\[[a-zA-Z0-9-]+:/ && (isFooter == 1) {
 88  		footerComment = 1
 89  	}
 90  
 91  	/]$/ && (footerComment == 1) {
 92  		footerComment = 2
 93  	}
 94  
 95  	# We have a non-blank line after blank lines. Handle this.
 96  	(blankLines > 0) {
 97  		print lines
 98  		for (i = 0; i < blankLines; i++) {
 99  			print ""
100  		}
101  
102  		lines = ""
103  		blankLines = 0
104  		isFooter = 1
105  		footerComment = 0
106  	}
107  
108  	# Detect that the current block is not the footer
109  	(footerComment == 0) && (!/^\[?[a-zA-Z0-9-]+:/ || /^[a-zA-Z0-9-]+:\/\//) {
110  		isFooter = 0
111  	}
112  
113  	{
114  		# We need this information about the current last comment line
115  		if (footerComment == 2) {
116  			footerComment = 0
117  		}
118  		if (lines != "") {
119  			lines = lines "\n";
120  		}
121  		lines = lines $0
122  	}
123  
124  	# Footer handling:
125  	# If the last block is considered a footer, splice in the Change-Id at the
126  	# right place.
127  	# Look for the right place to inject Change-Id by considering
128  	# CHANGE_ID_AFTER. Keys listed in it (case insensitive) come first,
129  	# then Change-Id, then everything else (eg. Signed-off-by:).
130  	#
131  	# Otherwise just print the last block, a new line and the Change-Id as a
132  	# block of its own.
133  	END {
134  		unprinted = 1
135  		if (isFooter == 0) {
136  			print lines "\n"
137  			lines = ""
138  		}
139  		changeIdAfter = "^(" tolower("'"$CHANGE_ID_AFTER"'") "):"
140  		numlines = split(lines, footer, "\n")
141  		for (line = 1; line <= numlines; line++) {
142  			if (unprinted && match(tolower(footer[line]), changeIdAfter) != 1) {
143  				unprinted = 0
144  				print "Change-Id: I'"$id"'"
145  			}
146  			print footer[line]
147  		}
148  		if (unprinted) {
149  			print "Change-Id: I'"$id"'"
150  		}
151  	}' "$MSG" > $T && mv $T "$MSG" || rm -f $T
152  }
153  _gen_ChangeIdInput() {
154  	echo "tree `git write-tree`"
155  	if parent=`git rev-parse "HEAD^0" 2>/dev/null`
156  	then
157  		echo "parent $parent"
158  	fi
159  	echo "author `git var GIT_AUTHOR_IDENT`"
160  	echo "committer `git var GIT_COMMITTER_IDENT`"
161  	echo
162  	printf '%s' "$clean_message"
163  }
164  _gen_ChangeId() {
165  	_gen_ChangeIdInput |
166  	git hash-object -t commit --stdin
167  }
168  
169  if ! grep -qi '^[[:space:]]*Signed-off-by:.\+<.\+@.\+>' "$MSG"; then
170  	printf "\nError: No Signed-off-by line in the commit message.\n"
171  	exit 1
172  fi
173  add_ChangeId