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 17CHANGE_ID_AFTER="Bug|Issue" 18MSG="$1" 19 20# Check for, and add if missing, a unique Change-Id 21# 22add_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 169if ! grep -qi '^[[:space:]]*Signed-off-by:.\+<.\+@.\+>' "$MSG"; then 170 printf "\nError: No Signed-off-by line in the commit message.\n" 171 exit 1 172fi 173add_ChangeId 174