From 9b3bc13e6b75463b70d56850ebdf25c39cbc48b3 Mon Sep 17 00:00:00 2001 From: Jean-Sebastien Legare Date: Wed, 5 Oct 2016 03:35:49 -0700 Subject: [PATCH 1/5] [quoting] symmetry. --- git-slack-hook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-slack-hook b/git-slack-hook index 06ff58b..f47b8b3 100755 --- a/git-slack-hook +++ b/git-slack-hook @@ -56,7 +56,7 @@ function notify() { fi # --- Get the revision types - newrev_type=$(git cat-file -t $newrev 2> /dev/null) + newrev_type=$(git cat-file -t "$newrev" 2> /dev/null) oldrev_type=$(git cat-file -t "$oldrev" 2> /dev/null) case "$change_type" in create|update) From a106937c5078e1b9ad70a6e394bd1c291c6d32e7 Mon Sep 17 00:00:00 2001 From: Jean-Sebastien Legare Date: Wed, 5 Oct 2016 03:36:10 -0700 Subject: [PATCH 2/5] [emacs] 2 character indent seems to be the norm in that repo. --- .dir-locals.el | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .dir-locals.el diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..93149b5 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,4 @@ + +(sh-mode . ((indent-tabs-mode . nil) + (sh-basic-offset . 2))) + From fd73dc54d30e79a895ae9ce01ae476d1708e0411 Mon Sep 17 00:00:00 2001 From: Jean-Sebastien Legare Date: Wed, 5 Oct 2016 03:37:09 -0700 Subject: [PATCH 3/5] [bugfix] Handle directory/repository names containing spaces. Quoting. --- git-slack-hook | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git-slack-hook b/git-slack-hook index f47b8b3..a2f9649 100755 --- a/git-slack-hook +++ b/git-slack-hook @@ -126,13 +126,13 @@ function notify() { # Repo name, either Gitolite or normal repo. if [ -n "$GL_REPO" ]; then # it's a gitolite repo - repodir=$(basename $(pwd)) + repodir=$(basename "$(pwd)") repo=$GL_REPO else - repodir=$(basename $(pwd)) + repodir=$(basename "$(pwd)") if [ "$repodir" == ".git" ]; then - repodir=$(dirname $PWD) - repodir=$(basename $repodir) + repodir=$(dirname "$PWD") + repodir=$(basename "$repodir") fi repo=${repodir%.git} fi From 01078c84bc96388b41c54547e960c2ccace28003 Mon Sep 17 00:00:00 2001 From: Jean-Sebastien Legare Date: Wed, 5 Oct 2016 11:51:28 -0700 Subject: [PATCH 4/5] [bugfix] Fix parsing of gitlog. Fix json string escapes. Fix sed in header. - if the replacement contains a '&' sed behaves differently. this fix avoids feeding URLs on the replacement side of sed commands. query strings may choke on it. ex. echo "123" | sed -e 's/2/T&O/g' # prints '1T2O3' - sed doesn't support non-greedy .* tokenization of git-log was broken. the command pipeline is replaced with tokenization using bash suffix match. easy to follow, fast, and straightforward to maintain. - the @,,,,@ and ;,,,,; separators have been replaced with pseudorandom boundary strings. safer. 7*15bits of entropy (poorman's uuid). - json escapes for just \n \" and \\ was insufficient. using python oneliner to escape unicode. --- git-slack-hook | 124 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 87 insertions(+), 37 deletions(-) diff --git a/git-slack-hook b/git-slack-hook index a2f9649..9ad871d 100755 --- a/git-slack-hook +++ b/git-slack-hook @@ -31,7 +31,52 @@ function help() { } function replace_variables() { - sed "s|%repo_path%|$repopath|g;s|%old_rev_hash%|$oldrev|g;s|%new_rev_hash%|$newrev|g;s|%rev_hash%|$newrev|g;s|%repo_prefix%|$repoprefix|g" + sed "s|%repo_path%|$repopath|g;s|%old_rev_hash%|$oldrev|g;s|%new_rev_hash%|$newrev|g;s|%rev_hash%|$newrev|g;s|%repo_prefix%|$repoprefix|g" +} + + +function json_string() { + # takes care of special characters and unicode escapes. assumes + # input is utf-8 (in practice, every commit message may have a + # different encoding). invalid utf-8 sequences are replaced + # ("\ufffd" or "\u00XX" depending on input). + python -c "import sys, json; print json.dumps(sys.argv[1].decode('utf-8', 'replace'))" "$1" +} + +function json_join() { + local IFS="," + echo -n "$*"; +} + +function parse_record() { + # parse_record STREAMVAR SEPARATOR [FIELDVAR]+ + # + # Works similarly to the `read` builtin. Instead of using IFS, it + # splits fields based on the separator given. It reads from and + # modifies in place variable named STREAMVAR. Subsequent parameters + # are variable _names_ used to store tokens, in order. + # + # variable STREAMVAR will be advanced by one record: []+[\n]* + # + + local -n stream=$1 + local -n field + local sep="$2" + local lf=$'\n' #newline + + shift 2 + + # field will point to a different field variable in turn + for field in "$@"; do + field="${stream%%${sep}*}" + # advance stream + stream="${stream:${#field}}" + stream="${stream:${#sep}}" + done + # eat trailing newlines between records + while [[ "${stream:0:1}" == $lf ]]; do + stream=${stream:1} + done } function notify() { @@ -192,19 +237,22 @@ function notify() { urlformat= if [ -n "$changeseturlpattern" -a -n "$reporoot" ]; then - if [[ $PWD == ${reporoot}* ]]; then + if [[ "$PWD" == ${reporoot}* ]]; then repopath=$PWD - base=$(basename $PWD) + base=$(basename "$PWD") if [ "$base" == ".git" ]; then repopath=$(dirname $repopath) fi - idx=$(echo $reporoot | wc -c | tr -d ' ') - repopath=$(echo $repopath | cut -c$idx-) - urlformat=$(echo $changeseturlpattern | replace_variables) + idx=$(echo "$reporoot" | wc -c | tr -d ' ') + repopath=$(echo "$repopath" | cut -c$idx-) + urlformat=$(echo "$changeseturlpattern" | replace_variables) if [ -n "$compareurlpattern" ]; then - comparelink=$(echo $compareurlpattern | replace_variables) - header=$(echo $header | sed -e "s|\([a-zA-Z0-9]\{1,\} new commit[s]\{0,1\}\)|\<$comparelink\|\\1\>|") + comparelink=$(echo "$compareurlpattern" | replace_variables) + # insert > after 'new commit(s)'. + # we avoid using comparelink on replacement of sed call (because of possible &) + header=$(echo "$header" | sed -e "s|\([a-zA-Z0-9]\{1,\} new commit[s]\{0,1\}\)\(.*\)|\\1\>\\2|") + header="<$comparelink|$header" fi else echo >&2 "$PWD is not in $reporoot. Not creating hyperlinks." @@ -242,28 +290,39 @@ function notify() { commitformat="%s" fi - # Process the log and escape double quotes and backslashes; assuming commit names/messages don't have five of the following: & ; @ - log_out=$( git log --pretty=format:"&&&&&%cN;;;;;${formattedurl}${commitformat}@@@@@" $countarg ${start}..${end} \ - | perl -p -e 's/@@@@@\n+/@@@@@/mg' \ - | sed -e 's/\\/\\\\/g' \ - | sed -e 's/"/\\"/g' \ - | sed -e 's/&&&&&\(.*\);;;;;/{ \"fallback\" : \"\", \"color\" : \"good\", \"fields\" : [{"title":"\1","value":"/g' \ - | sed -e 's/@@@@@/","short":false},]},/g' \ - | sed -e 's/,\]/]/' ) + sep="boundary-${RANDOM}-${RANDOM}-${RANDOM}-${RANDOM}-${RANDOM}-${RANDOM}-${RANDOM}--" + log_out=$(git log --pretty=format:"%cN${sep}${formattedurl}${commitformat}${sep}" $countarg "${start}..${end}") + attachment_arr=() + while [[ -n "${log_out}" ]]; do + title="" + value="" + parse_record log_out "${sep}" title value + + attachment_arr+=(' +{ + "fallback": "", + "color": "good", + "fields": [{ + "title": '"$(json_string "$title")"', + "value": '"$(json_string "$value")"', + "short": false + }] +}') + done - attachments="[${log_out%?}]" fi - if [ -n "${attachments}" ] && [[ "${attachments}" != "" ]]; then - msg=$(echo -e "\"text\":\"${header}\", \"attachments\" : $attachments") + if [[ "${#attachment_arr[@]}" -gt 0 ]]; then + attachments="[ $( json_join "${attachment_arr[@]}" ) ]" + msg=' +"text": '"$(json_string "$header")"', +"attachments": '"$attachments" else - msg=$(echo -e "\"text\":\"${header}\"") + msg=' +"text": '"$(json_string "${header}")" fi - # slack API uses \n substitution for newlines - msg=$(echo -n "${msg}" | perl -p -e 's/\n/\\n/mg') - webhook_url=$(git config --get hooks.slack.webhook-url) channel=$(git config --get hooks.slack.channel) username=$(git config --get hooks.slack.username) @@ -278,21 +337,12 @@ function notify() { payload="{${msg}" - if [ -n "$channel" ]; then - payload="$payload, \"channel\": \"$channel\"" - fi - - if [ -n "$username" ]; then - payload="$payload, \"username\": \"$username\"" - fi - - if [ -n "$iconurl" ]; then - payload="$payload, \"icon_url\": \"$iconurl\"" - elif [ -n "$iconemoji" ]; then - payload="$payload, \"icon_emoji\": \"$iconemoji\"" - fi + [[ -z "$channel" ]] || payload+=',"channel":'"$(json_string "$channel")" + [[ -z "$username" ]] || payload+=',"username":'"$(json_string "$username")" + [[ -z "$iconurl" ]] || payload+=',"icon_url":'"$(json_string "$iconurl")" + [[ -z "$iconemoji" ]] || payload+=',"icon_emoji":'"$(json_string "$iconemoji")" - payload="$payload}" + payload+="}" if [ -n "$DEBUG" ]; then echo "POST $webhook_url" From 92912b3c682ce9d000ffe6d2330c1ecac162671d Mon Sep 17 00:00:00 2001 From: Jean-Sebastien Legare Date: Wed, 5 Oct 2016 11:52:45 -0700 Subject: [PATCH 5/5] [bugfix] Payload needs to be urlencoded. Safe to use with '&' in urls now. URLs containing '&' would trigger 400 invalid_payload slack errors. POST body needs to be urlencoded. --- git-slack-hook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-slack-hook b/git-slack-hook index 9ad871d..f7459bd 100755 --- a/git-slack-hook +++ b/git-slack-hook @@ -351,7 +351,7 @@ function notify() { fi curl -s \ - -d "payload=$payload" \ + --data-urlencode "payload=$payload" \ "$webhook_url" \ >/dev/null