|
| 1 | +#!/bin/bash |
| 2 | +usage(){ |
| 3 | +cat<<EOF |
| 4 | +
|
| 5 | + Tag & build a release from a git repository |
| 6 | + Usage: |
| 7 | + buildRelease.sh [options] |
| 8 | +
|
| 9 | + options: |
| 10 | + -r <remote> Remote repository to update, default to origin. |
| 11 | + -b <gitbranch> Git branch to be tagged, default to master. |
| 12 | + -t <tag> Tag name to be created. |
| 13 | + -e <tagRegExp> A regular expresion to describe the tag naming schema |
| 14 | + -n Dry run mode. No actions would be performed on the reposiory |
| 15 | + -h Show this help. |
| 16 | +
|
| 17 | +
|
| 18 | +EOF |
| 19 | +} |
| 20 | + |
| 21 | +set -e |
| 22 | + |
| 23 | +# Set defaults: |
| 24 | +REMOTE='origin' |
| 25 | +BRANCH='master' |
| 26 | +TAG='' |
| 27 | +TAGREGEXP="v?[0-9]+\.[0-9]+\.[0-9]+(rc[0-9]+)*$" |
| 28 | +DRYRUN=false |
| 29 | + |
| 30 | +while getopts ":r:b:t:e:hn" opt; do |
| 31 | + case ${opt} in |
| 32 | + r) |
| 33 | + REMOTE=$OPTARG |
| 34 | + REPOURL=`git remote get-url $REMOTE` |
| 35 | + ;; |
| 36 | + b) |
| 37 | + BRANCH=$OPTARG ;; |
| 38 | + t) |
| 39 | + TAG=$OPTARG ;; |
| 40 | + e) |
| 41 | + TAGREGEXP=$OPTARG ;; |
| 42 | + n) |
| 43 | + DRYRUN=true ;; |
| 44 | + h) |
| 45 | + usage; exit 0 ;; |
| 46 | + \? ) |
| 47 | + echo -e "\nERROR: Invalid Option: -$OPTARG\n" |
| 48 | + usage |
| 49 | + exit 1 ;; |
| 50 | + : ) |
| 51 | + echo -e "\nERROR: Invalid Option: -$OPTARG requires an argument\n" |
| 52 | + usage |
| 53 | + exit 1 ;; |
| 54 | + esac |
| 55 | +done |
| 56 | +# shift to the last parsed option, so we can consume the non-parametric |
| 57 | +# arguments passed to the call with a regular shift |
| 58 | +# shift $(expr $OPTIND - 1 ) |
| 59 | + |
| 60 | +# Check if TAG was provided; |
| 61 | +[[ -z $TAG ]] && { usage ; echo "ERROR: No TAG provided"; exit 3 ;} |
| 62 | + |
| 63 | +# Check if the TAG provided follows the tag regular expression: |
| 64 | +[[ $TAG =~ $TAGREGEXP ]] || { echo "ERROR: TAG:$TAG does not match TAGREGEXP: $TAGREGEXP"; exit 4 ;} |
| 65 | + |
| 66 | +# Check if remote exists, and create it if it is missing: |
| 67 | +[[ `git remote` =~ $REMOTE ]] || { |
| 68 | + echo "ERROR: Missing remote: $REMOTE." |
| 69 | + exit 2 |
| 70 | +} |
| 71 | + |
| 72 | +# Find the final real repoUrl and protocol |
| 73 | +repoUrl=`git remote get-url $REMOTE` |
| 74 | +repoUrlProto=${repoUrl%%:*} |
| 75 | +repoUrlProto=${repoUrlProto%%@*} |
| 76 | + |
| 77 | +echo |
| 78 | +echo "=======================================================" |
| 79 | +echo "You are about to create:" |
| 80 | +echo "RELEASE : $TAG" |
| 81 | +echo "REPOSITORY: $repoUrl" |
| 82 | +echo |
| 83 | +echo -n "Continue? [y]: " |
| 84 | +read x && [[ $x =~ (n|no|nO|N|No|NO) ]] && exit 101 |
| 85 | +echo "-------------------------------------------------------" |
| 86 | +echo |
| 87 | + |
| 88 | +# Find the current repository name: |
| 89 | +repoName=`echo $repoUrl | xargs basename -s .git` |
| 90 | +echo repoName: $repoName |
| 91 | + |
| 92 | +# Find the current repository owner: |
| 93 | +repoOwner=${repoUrl##*github.com:} |
| 94 | +repoOwner=${repoOwner##*github.com/} |
| 95 | +repoOwner=${repoOwner%%/$repoName*} |
| 96 | +echo repoOwner: $repoOwner |
| 97 | + |
| 98 | +# Build the current repository api url |
| 99 | +repoApiUrl="https://api.github.com/repos/$repoOwner/$repoName" |
| 100 | +echo repoApiUrl: $repoApiUrl |
| 101 | + |
| 102 | +# Check whether we are in the correct directory |
| 103 | +[[ $(git rev-parse --show-toplevel) == $(realpath $PWD) ]] || { |
| 104 | + echo "ERROR: Not in root directory $(git rev-parse --show-toplevel)!" |
| 105 | + exit 5 |
| 106 | +} |
| 107 | + |
| 108 | +# Check if tag exists |
| 109 | +git show-ref --tags --quiet -- $TAG && { |
| 110 | + echo "ERROR: Tag $TAG exists!" |
| 111 | + exit 6 |
| 112 | +} |
| 113 | + |
| 114 | +echo "Checking out branch: $BRANCH" |
| 115 | +git checkout $BRANCH |
| 116 | + |
| 117 | +echo "Pulling from upstream" |
| 118 | +git pull $REMOTE $BRANCH |
| 119 | +git fetch $REMOTE |
| 120 | + |
| 121 | +# Fetch the last tag commit (hash id plus tag name) |
| 122 | +lastRelCommitLine=$(git log -n1 --oneline --no-decorate -E --grep=$TAGREGEXP) |
| 123 | +lastRelCommit=$(echo ${lastRelCommitLine} | awk '{print $1}') |
| 124 | +lastVersion=$(echo ${lastRelCommitLine} | awk '{print $2}') |
| 125 | + |
| 126 | +# If no dedicated release commit is found, just take the last tag created. |
| 127 | +[[ -n $lastRelCommit ]] || { |
| 128 | + lastTag=$(git describe --long --always --tags) |
| 129 | + lastTag=${lastTag%%-*} |
| 130 | + lastTagDescribe=$(git describe --long --always --tags $lastTag) |
| 131 | + lastVersion=${lastTagDescribe%%-*} |
| 132 | + lastRelCommit=${lastTagDescribe##*-g} |
| 133 | +} |
| 134 | + |
| 135 | +echo lastRelCommitLine: $lastRelCommitLine |
| 136 | +echo lastTagDescribe: $lastTagDescribe |
| 137 | +echo lastRelCommit: $lastRelCommit |
| 138 | +echo lastVersion: $lastVersion |
| 139 | + |
| 140 | +# Generating the CHANGES file |
| 141 | +echo "Generating CHANGES file ..." |
| 142 | +changesFile=CHANGES.md |
| 143 | +changesFileTmp=$(mktemp -t $repoName.${TAG}.XXXXX) |
| 144 | + |
| 145 | +echo "### **${lastVersion} to ${TAG}:**" >> $changesFileTmp |
| 146 | + |
| 147 | +# Grab all the commit hashes, subject and author since the last release |
| 148 | +changesCommitsFile=${changesFileTmp}.commits |
| 149 | +git log --no-merges --pretty=format:'%H %s (%aN)' ${lastRelCommit}.. >> $changesCommitsFile |
| 150 | +echo "" >> $changesCommitsFile |
| 151 | + |
| 152 | +echo changesFileTmp: $changesFileTmp |
| 153 | +echo changesCommitsFile: $changesCommitsFile |
| 154 | + |
| 155 | +# Use github public API to fetch pull request # from commit hash |
| 156 | +cat $changesCommitsFile | while read commitLine; do |
| 157 | + [[ -z "$commitLine" ]] && continue # line is empty |
| 158 | + hashId=$(echo $commitLine | awk '{print $1}') |
| 159 | + # remove hash id from the commit line |
| 160 | + commitLine=$(echo $commitLine | sed "s/$hashId/ -/") |
| 161 | + # NOTE: If we do not find a proper Pull Request Url it must have been a commit |
| 162 | + # directly to a branch. Then we must populate the branch name instead of prUrl |
| 163 | + prUrl=$(curl -s $repoApiUrl/commits/$hashId/pulls | jq -r '.[]["html_url"]') |
| 164 | + if [[ -n $prUrl ]]; then |
| 165 | + prNum=`basename $prUrl` |
| 166 | + echo "$commitLine [#$prNum]($prUrl)" >> $changesFileTmp |
| 167 | + else |
| 168 | + commitUrl=$(curl -s $repoApiUrl/commits/$hashId | jq -r '.["html_url"]') |
| 169 | + commitBranch=$(curl -s $repoApiUrl/commits/$hashId/branches-where-head | jq -r '.[]["name"]') |
| 170 | + echo "$commitLine [${hashId::6}]($commitUrl) on $commitBranch" >> $changesFileTmp |
| 171 | + fi |
| 172 | +done |
| 173 | +echo -en '\n\n' >> $changesFileTmp |
| 174 | + |
| 175 | +# Append the original CHANGES content and later swap the files if not in DRYRUN mode |
| 176 | +[[ -f $changesFile ]] && cat $changesFile >> $changesFileTmp |
| 177 | + |
| 178 | +${EDITOR:-vi} $changesFileTmp || { |
| 179 | + echo "ERROR: User canceled CHANGES update" |
| 180 | + exit 7 |
| 181 | +} |
| 182 | + |
| 183 | +# Apply all the changes to the local tree and tag |
| 184 | +echo |
| 185 | +echo "=======================================================" |
| 186 | +echo "These are the commits to be included in the tag: $TAG:" |
| 187 | +git log --pretty=format:' - %s' ${lastRelCommit}.. |
| 188 | +echo |
| 189 | + |
| 190 | +# Exit here if we are in DRYRUN mode. The rest of the script is intrusive to the local tree |
| 191 | +$DRYRUN && exit 0 |
| 192 | + |
| 193 | +echo -n "Continue? [n]: " |
| 194 | +read x && [[ $x =~ (y|Y|yes|Yes|YES) ]] || { echo "ERROR: User interrupt"; exit 102 ;} |
| 195 | +echo "-------------------------------------------------------" |
| 196 | +echo |
| 197 | + |
| 198 | +echo "Saving the new CHANGES file ..." |
| 199 | +cp $changesFileTmp $changesFile |
| 200 | +git add $changesFile |
| 201 | + |
| 202 | +echo "Creating a tag commit ..." |
| 203 | +git commit -a -s -m "$TAG" |
| 204 | + |
| 205 | +echo "Tagging release ..." |
| 206 | +git log --pretty=format:' - %s' ${lastRelCommit}.. | git tag -a $TAG -F - |
| 207 | + |
| 208 | +echo "Pushing to ${REMOTE} ..." |
| 209 | +git push --tags ${REMOTE} ${BRANCH} |
| 210 | +set +e |
| 211 | + |
| 212 | +echo "$TAG tagged" |
| 213 | + |
| 214 | +echo |
| 215 | + |
| 216 | +exit 0 |
0 commit comments