| #!/usr/bin/env sh | |
| # Part of Gerrit Code Review (http://code.google.com/p/gerrit/) | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # | |
| CHANGE_ID_AFTER="Bug|Issue" | |
| MSG="$1" | |
| # Check for, and add if missing, a unique Change-Id | |
| # | |
| add_ChangeId() { | |
| clean_message=`sed -e ' | |
| /^diff --git a\/.*/{ | |
| s/// | |
| q | |
| } | |
| /^Signed-off-by:/d | |
| /^#/d | |
| ' "$MSG" | git stripspace` | |
| if test -z "$clean_message" | |
| then | |
| return | |
| fi | |
| # Does Change-Id: already exist? if so, exit (no change). | |
| if grep -i '^Change-Id: I[0-9a-f]\{40\}$' "$MSG" >/dev/null | |
| then | |
| return | |
| fi | |
| id=`_gen_ChangeId` | |
| T="$MSG.tmp.$$" | |
| AWK=awk | |
| if [ -x /usr/xpg4/bin/awk ]; then | |
| # Solaris AWK is just too broken | |
| AWK=/usr/xpg4/bin/awk | |
| fi | |
| # How this works: | |
| # - parse the commit message as (textLine+ blankLine*)* | |
| # - assume textLine+ to be a footer until proven otherwise | |
| # - exception: the first block is not footer (as it is the title) | |
| # - read textLine+ into a variable | |
| # - then count blankLines | |
| # - once the next textLine appears, print textLine+ blankLine* as these | |
| # aren't footer | |
| # - in END, the last textLine+ block is available for footer parsing | |
| $AWK ' | |
| BEGIN { | |
| # while we start with the assumption that textLine+ | |
| # is a footer, the first block is not. | |
| isFooter = 0 | |
| footerComment = 0 | |
| blankLines = 0 | |
| } | |
| # Skip lines starting with "#" without any spaces before it. | |
| /^#/ { next } | |
| # Skip the line starting with the diff command and everything after it, | |
| # up to the end of the file, assuming it is only patch data. | |
| # If more than one line before the diff was empty, strip all but one. | |
| /^diff --git a/ { | |
| blankLines = 0 | |
| while (getline) { } | |
| next | |
| } | |
| # Count blank lines outside footer comments | |
| /^$/ && (footerComment == 0) { | |
| blankLines++ | |
| next | |
| } | |
| # Catch footer comment | |
| /^\[[a-zA-Z0-9-]+:/ && (isFooter == 1) { | |
| footerComment = 1 | |
| } | |
| /]$/ && (footerComment == 1) { | |
| footerComment = 2 | |
| } | |
| # We have a non-blank line after blank lines. Handle this. | |
| (blankLines > 0) { | |
| print lines | |
| for (i = 0; i < blankLines; i++) { | |
| print "" | |
| } | |
| lines = "" | |
| blankLines = 0 | |
| isFooter = 1 | |
| footerComment = 0 | |
| } | |
| # Detect that the current block is not the footer | |
| (footerComment == 0) && (!/^\[?[a-zA-Z0-9-]+:/ || /^[a-zA-Z0-9-]+:\/\//) { | |
| isFooter = 0 | |
| } | |
| { | |
| # We need this information about the current last comment line | |
| if (footerComment == 2) { | |
| footerComment = 0 | |
| } | |
| if (lines != "") { | |
| lines = lines "\n"; | |
| } | |
| lines = lines $0 | |
| } | |
| # Footer handling: | |
| # If the last block is considered a footer, splice in the Change-Id at the | |
| # right place. | |
| # Look for the right place to inject Change-Id by considering | |
| # CHANGE_ID_AFTER. Keys listed in it (case insensitive) come first, | |
| # then Change-Id, then everything else (eg. Signed-off-by:). | |
| # | |
| # Otherwise just print the last block, a new line and the Change-Id as a | |
| # block of its own. | |
| END { | |
| unprinted = 1 | |
| if (isFooter == 0) { | |
| print lines "\n" | |
| lines = "" | |
| } | |
| changeIdAfter = "^(" tolower("'"$CHANGE_ID_AFTER"'") "):" | |
| numlines = split(lines, footer, "\n") | |
| for (line = 1; line <= numlines; line++) { | |
| if (unprinted && match(tolower(footer[line]), changeIdAfter) != 1) { | |
| unprinted = 0 | |
| print "Change-Id: I'"$id"'" | |
| } | |
| print footer[line] | |
| } | |
| if (unprinted) { | |
| print "Change-Id: I'"$id"'" | |
| } | |
| }' "$MSG" > $T && mv $T "$MSG" || rm -f $T | |
| } | |
| _gen_ChangeIdInput() { | |
| echo "tree `git write-tree`" | |
| if parent=`git rev-parse "HEAD^0" 2>/dev/null` | |
| then | |
| echo "parent $parent" | |
| fi | |
| echo "author `git var GIT_AUTHOR_IDENT`" | |
| echo "committer `git var GIT_COMMITTER_IDENT`" | |
| echo | |
| printf '%s' "$clean_message" | |
| } | |
| _gen_ChangeId() { | |
| _gen_ChangeIdInput | | |
| git hash-object -t commit --stdin | |
| } | |
| if ! grep -qi '^[[:space:]]*Signed-off-by:.\+<.\+@.\+>' "$MSG"; then | |
| printf "\nError: No Signed-off-by line in the commit message.\n" | |
| exit 1 | |
| fi | |
| add_ChangeId |