1#!/bin/bash
2set -e
3set -u
4
5# this will match a version or pre-release version e.g.
6# "19.2.4" or "19.2.4~pre13.xyz"
7version_regexp='([[:digit:]]+\.){2}[[:digit:]]+(~[[:alnum:]]+)?'
8
9# this will only match a stable version e.g.
10# "18.2.7"
11stable_version_regexp='([[:digit:]]+\.){2}[[:digit:]]+'
12
13topdir="$(dirname "$0")"
14prog="$(basename "$0")"
15
16git="${GIT:-$(type -p git)}"
17cmake="${CMAKE:-$(type -p cmake)}"
18
19pushd "$topdir" >/dev/null
20
21log_info() {
22  echo -e "\e[36m$prog: INFO:  $*\e[0m" >&2
23}
24
25log_warn() {
26  echo -e "\e[33m$prog: WARN:  $*\e[0m" >&2
27}
28
29log_fatal() {
30  echo -e "\e[31m$prog: FATAL: $*\e[0m" >&2
31  exit 1
32}
33
34# wrap stdin in a nice blue box
35print_block() {
36  local s='\e[44m\e[97m' e='\e[0m'
37  printf "\n%b┏" "$s"; printf -- "━%.0s" {1..77}; printf "┓%b\n" "$e"
38  printf "%b┃  %-73s  ┃%b\n" "$s" '' "$e"
39  while read line; do
40    printf "%b┃  %-73s  ┃%b\n" "$s" "$line" "$e"
41  done
42  printf "%b┃  %-73s  ┃%b\n" "$s" '' "$e"
43  printf "%b┗" "$s"; printf -- "━%.0s" {1..77}; printf "┛%b\n\n" "$e"
44}
45
46# get_next_version accepts a stable version as its only parameter.
47# it will then return the patch-version that follows after the provided version.
48get_next_version() {
49  local in_parts out_parts last
50  IFS=. in_parts=($1)  # split into an array
51  ((last=${#in_parts[@]} - 1 )) # get index of last element
52  out_parts=()
53  # concatenate up to, but not including last element
54  for (( i=0; i<last; i++ )); do
55    out_parts+=("${in_parts[i]}")
56  done
57  out_parts+=($(( in_parts[i]+1 ))) # add last element incremented by one
58  IFS=. echo "${out_parts[*]}" # join array to string
59}
60
61confirm() {
62  echo -ne "  \e[44m\e[97m $* (y/n) \e[0m"
63  read -n 1 -r
64  echo -e "\n"
65  [[ $REPLY =~ ^[Yy]$ ]]
66}
67
68replace_define_expr() {
69  # generate an sed expression to replace the string value of a single-line
70  # C preprocessor object macro
71  # requires sed --regexp-extended
72  echo "s/(#define $1[^\"]+)\"[^\"]+\"/\1\"$2\"/"
73}
74
75if [ -z "$git" -o ! -x "$git" ]; then
76  log_fatal "git not found."
77elif [ -z "$cmake" -o ! -x "$cmake" ]; then
78  log_fatal "cmake not found."
79elif [ ! -e .git ]; then
80  log_fatal "This is not a git working copy."
81elif [ -n "$("$git" status --short)" ]; then
82  log_fatal "This script must be run in a clean working copy."
83fi
84
85# get the git remote name for the upstream GitHub repository
86git_remote="$("$git" remote -v | \
87  awk '/git@github.com:\/?bareos\/bareos.git \(push\)/ { print $1 }')"
88if [ -z "$git_remote" ]; then
89  # warn if not found and set a default for sane messages"
90  log_warn "Could not find the upstream repository in your git remotes."
91  git_remote="<remote>"
92fi
93
94
95# we try to detect the version that should be released.
96# for this we retrieve the version of the source-tree we're running in and strip
97# any prerelease suffix.
98autodetect_version=$("$git" describe --tags --match "WIP/*" \
99                     | grep --extended-regexp --only-matching "$version_regexp")
100if [ -n "${1:-}" ]; then
101    log_info "Using provided version $1."
102    version="$1"
103elif [ -n "$autodetect_version" ]; then
104  log_info "Autodetected version $autodetect_version based on WIP-tag."
105  version="$autodetect_version"
106else
107  log_fatal Cannot autodetect version and no version passed on cmdline
108fi
109
110if ! grep --quiet --extended-regexp "^$version_regexp\$" <<< "$version"; then
111  log_fatal "Version $1 does not match the expected pattern"
112fi
113
114git_branch="$(git rev-parse --abbrev-ref=strict HEAD)"
115if ! grep --quiet --fixed-strings "$git_branch" <<< "bareos-$version"; then
116  log_warn "Git branch $git_branch is not the release branch for $version."
117fi
118
119release_tag="Release/${version/\~/-}"
120release_ts="$(date --utc --iso-8601=seconds | sed --expression='s/T/ /;s/+.*$//')"
121release_message="Release $version"
122
123# Only if we are preparing a stable release
124if grep --quiet --extended-regexp "^$stable_version_regexp\$" <<< "$version"; then
125  wip_enable=1
126  wip_version="$(get_next_version "$version")"
127  wip_tag="WIP/$wip_version-pre"
128  wip_message="Start development of $wip_version"
129else
130  log_info "Will not generate a new WIP tag for a pre-release"
131  wip_enable=0
132  wip_version="(none)"
133  wip_tag="(none)"
134  wip_message="(none)"
135fi
136
137print_block << EOT
138You are preparing a release of Bareos. As soon as you push the results of
139this script to the official git repository, the new version is released.
140If you decide not to push, nothing will be released.
141
142The release you are preparing will have the following settings:
143   Version:      "$version"
144   Release tag:  "$release_tag"
145   Release time: "$release_ts"
146   Release msg:  "$release_message"
147
148If this is not a pre-release a new work-in-progress tag will be created:
149   Next version: "$wip_version"
150   WIP tag:      "$wip_tag"
151   WIP message:  "$wip_message"
152
153
154This script will do the following:
155  1. Patch version.h with the correct version and timestamps
156  2. Create a commit containing the patched version.h
157  3. Add an empty commit for a WIP tag (not for pre-releases)
158  4. Set the release tag
159  5. Set the WIP tag (not for pre-releases)
160
161Please make sure you're on the right branch before continuing and review
162the commits, tags and branch pointers before you push!
163
164For a major release you should be on the release-branch, not the master
165branch. While you can move around branch pointers later, it is a lot
166easier to branch first.
167EOT
168
169if ! confirm "Do you want to proceed?"; then
170  log_info "Exiting due to user request."
171  exit 0
172fi
173
174original_commit="$(git rev-parse HEAD)"
175log_info "if you want to rollback the commits" \
176  "you can run 'git reset --soft $original_commit'"
177
178version_h=core/src/include/version.h
179
180define_BDATE="$(LC_TIME=C date --date="$release_ts+0000" "+%d %B %Y")"
181define_LSMDATE="$(LC_TIME=C date --date="$release_ts+0000" "+%d%b%y")"
182define_BYEAR="$(LC_TIME=C date --date="$release_ts+0000" "+%Y")"
183
184sed \
185  --in-place \
186  --regexp-extended \
187  --expression="$(replace_define_expr VERSION "$version")" \
188  --expression="$(replace_define_expr BDATE "$define_BDATE")" \
189  --expression="$(replace_define_expr LSMDATE "$define_LSMDATE")" \
190  --expression="$(replace_define_expr BYEAR "$define_BYEAR")" \
191  "$version_h"
192
193"$git" diff -- "$version_h"
194
195if ! confirm "Should we proceed with the above changes applied?"; then
196  log_info "Exiting due to user request."
197  exit 0
198fi
199
200"$git" add \
201  --no-all \
202  -- \
203  "$version_h"
204
205"$git" commit \
206  --quiet \
207  --only \
208  --date="$release_ts" \
209  -m "$release_message" \
210  -- \
211  "$version_h"
212
213release_commit="$(git rev-parse HEAD)"
214log_info "commit for release tag will be $release_commit"
215
216if [ "$wip_enable" -eq 1 ]; then
217  "$git" commit \
218    --quiet \
219    --allow-empty \
220    --date="$release_ts" \
221    -m "$wip_message"
222
223  wip_commit="$(git rev-parse HEAD)"
224  log_info "commit for WIP tag will be $wip_commit"
225fi
226
227echo
228echo The log for the new commits is:
229echo
230
231"$git" log \
232  --decorate \
233  --graph \
234  "${original_commit}..HEAD"
235
236echo
237
238log_info "Creating release tag for $release_commit named $release_tag"
239"$git" tag "$release_tag" "$release_commit"
240
241if [ -n "${wip_commit:-}" ]; then
242  log_info "Creating WIP tag for $wip_commit named $wip_tag"
243  "$git" tag "$wip_tag" "$wip_commit"
244fi
245
246(
247  cat <<EOT
248To publish your new release, you need to do the follwing git push steps:
249  git push $git_remote HEAD
250  git push $git_remote $release_tag
251EOT
252  if [ -n "${wip_commit:-}" ]; then
253    echo "git push $git_remote $wip_tag"
254  fi
255) | print_block
256