|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +# The script to merge in upstream changes. |
| 4 | +# Provide an argument of the release major-minor, in the format of `vX.Y`. For example: |
| 5 | +# `v1.123`. Detection will run looking for upstream release branch of that version. |
| 6 | +# |
| 7 | +# Three scenarios are handled by this script: |
| 8 | +# 1. New minor version(updating from `v1.123` to `v1.124`) |
| 9 | +# 2. Existing minor version with an upstream patch increment(updating from `v1.123.0` to |
| 10 | +# `v1.123.1`) |
| 11 | +# 3. Existing patch version with a local Famedly level patch increment(updating from |
| 12 | +# `v1.123.1_1` to `v1.123.1_2`) |
| 13 | +# |
| 14 | +# If the provided release version does not exist on this repo, it will be created. |
| 15 | +# If it already exists on this repo, even remotely, it will be found and then checked |
| 16 | +# out. |
| 17 | +# After resolving this branch, the appropriate upstream changes will be merged in if |
| 18 | +# they exist. If a merge conflict arises from merging in upstream, the script will exit. |
| 19 | +# Once any conflicts are resolved, `git add` and `git merge --continue` need to be ran. |
| 20 | +# Then, run this script again with the same argument to proceed with the next step. If |
| 21 | +# you have not correctly commited the changes from a conflict, they will be lost. |
| 22 | +# When there are no upstream changes and only local changes, the Famedly |
| 23 | +# patch level is incremented. No tags are generated in this script. |
| 24 | +# After the merge is completed, the CHANGES.md file is updated to include any changes |
| 25 | +# exclusive to Famedly's repo. These changes will be placed at the bottom of the current |
| 26 | +# most entry. Successive Famedly additions will stay in order, appended after. |
| 27 | +set -e |
| 28 | + |
| 29 | +release_name=$1 |
| 30 | + |
| 31 | +if [ -z "${release_name}" ]; then |
| 32 | + echo "Usage: $0 <release_name in vX.Y format>" |
| 33 | + exit 1 |
| 34 | +fi |
| 35 | + |
| 36 | +if [ "${release_name}" = "-h" ]; then |
| 37 | + echo "Usage: $0 <release_name in vX.Y format>" |
| 38 | + exit 0 |
| 39 | +fi |
| 40 | + |
| 41 | +# The minor number of the version, so of the release_name above which looks like `vX.Y`, |
| 42 | +# we are peeling off the `Y` for further use |
| 43 | +expected_minor=${release_name##*.} |
| 44 | + |
| 45 | +# What our branch name will be. Upstream uses the format of `release-vX.Y` so to avoid |
| 46 | +# confusion we will pre-pend 'famedly' to that and use a `/` to namespace it. Like this: |
| 47 | +# `famedly-release/vX.Y`. We do want patch levels and hotfixes to live on the same |
| 48 | +# branch as the release major.minor |
| 49 | +release_branch_name="famedly-release/${release_name}" |
| 50 | + |
| 51 | +# The upstream repo's version appropriate branch name |
| 52 | +upstream_release_branch_name="release-${release_name}" |
| 53 | + |
| 54 | +echo -e "\e[32m>>>> fetching origin branches\e[0m" |
| 55 | +# If the release_name that was passed already exists as a tag, this will fatal error. |
| 56 | +# Make sure it does not legitimately exist, or that you really mean to replace it before |
| 57 | +# running `git tag -d <release_name>` to remove that one single tag. XXX: this may be wrong now, double check |
| 58 | +git fetch --tags origin |
| 59 | + |
| 60 | +echo -e "\e[32m>>>> fetching upstream branches\e[0m" |
| 61 | +git fetch --tags upstream |
| 62 | + |
| 63 | +echo -e "\e[32m>>>> checkout master branch\e[0m" |
| 64 | +git checkout master |
| 65 | +echo -e "\e[32m>>>> resetting master branch\e[0m" |
| 66 | +git reset --hard |
| 67 | + |
| 68 | +echo -e "\e[32m>>>> find/checkout release branch\e[0m" |
| 69 | +# Temporarily disable error catching, as it will cause the script to halt |
| 70 | +# Normally this is not needed, but apparently our chosen branch name for famedly |
| 71 | +# releases causes fatal errors if it does not already exist. I believe it is caused by |
| 72 | +# the '/' in the branch name and how that can be a confusing reference for git to try |
| 73 | +# and sort out. |
| 74 | +# Specifically `famedly-release/vX.Y` comes across as 'fatal' instead of just |
| 75 | +# 'non-existent'. I choose to blame the '/' |
| 76 | +set +e |
| 77 | + |
| 78 | +# Expect this a '0' for exists and a '1' for not-exists. Fatal is a '128'. We apply a |
| 79 | +# NOT so the condition only runs if there was an error |
| 80 | +if ! git switch "$release_branch_name"; then |
| 81 | + echo "branch did not exist, creating" |
| 82 | + # -c creates the branch at our current checkout. Which should be a clean checkout of |
| 83 | + # the master branch from a moment ago |
| 84 | + git switch -c "$release_branch_name" |
| 85 | +fi |
| 86 | +# Have to re-enable error catching in case something else breaks |
| 87 | +set -e |
| 88 | + |
| 89 | +# Fetch existing tags on this branch. If this was a new branch from main, ours will be the |
| 90 | +# last tag added. If this was an existing branch we are appending to, ours will still be |
| 91 | +# the last tag added. Note that this may differ from what we are releasing if this the |
| 92 | +# initial version the branch contains. For example, if we are merging in `v1.124`, this |
| 93 | +# will be `v1.123.0_1` or similar |
| 94 | +last_tag_on_this_branch=$(git describe --tags --abbrev=0 --match v*.*.*_*) |
| 95 | + |
| 96 | +# Our patch level version, everything after the `_` |
| 97 | +famedly_patch_level=${last_tag_on_this_branch#*_} |
| 98 | + |
| 99 | +# The major, minor and patch level, so `vX.Y.Z` |
| 100 | +long_ver=${last_tag_on_this_branch%_*} |
| 101 | + |
| 102 | +# Just the patch level integer, so `Z` |
| 103 | +patch_level=${long_ver##*.} |
| 104 | + |
| 105 | +# The `vX.Y`. |
| 106 | +major_minor=${long_ver%.*} |
| 107 | + |
| 108 | +# and just the `Y` |
| 109 | +minor_ver=${major_minor##*.} |
| 110 | + |
| 111 | + |
| 112 | +# First determine if the upstream branch has incremented it's patch level. These are in |
| 113 | +# the format of `vX.Y.Z` |
| 114 | +upstreams_last_tag=$(git ls-remote --tags upstream "$release_name"* | grep -o 'refs/tags/v[0-9]*\.[0-9]*\.[0-9]*' | sort -rV | head -n1 | cut --delimiter='/' --fields=3) |
| 115 | +# This should just be the `Z` |
| 116 | +upstream_patch_level=${upstreams_last_tag##*.} |
| 117 | +# The `vX.Y` |
| 118 | +upstream_major_minor=${upstreams_last_tag%.*} |
| 119 | +# And just the `Y` |
| 120 | +upstream_minor_ver=${upstream_major_minor##.*} |
| 121 | + |
| 122 | +# if upstream patch level has increased, we bump ours too |
| 123 | +if [[ "$expected_minor" != "$minor_ver" ]]; then |
| 124 | + # Must have been a version bump which means this is a new minor version release. In |
| 125 | + # this case, it's likely that it is the same as what upstream's is. This resets the |
| 126 | + # Famedly patch level. |
| 127 | + expected_new_version_tag="${upstreams_last_tag}_1" |
| 128 | + echo "Likely new minor version $expected_new_version_tag" |
| 129 | + |
| 130 | +elif [[ "$upstream_patch_level" -gt "$patch_level" ]]; then |
| 131 | + # No minor version bump but there was a patch level version bump, increment our |
| 132 | + # expected patch level and reset the Famedly patch level. |
| 133 | + expected_new_version_tag="${upstreams_last_tag}_1" |
| 134 | + echo "Likely new patch version $expected_new_version_tag" |
| 135 | + |
| 136 | +else |
| 137 | + # No minor and no patch level increase, this is probably just a famedly patch level |
| 138 | + # increase. |
| 139 | + increment_patch_level=$(( famedly_patch_level + 1)) |
| 140 | + expected_new_version_tag="${upstreams_last_tag}_$increment_patch_level" |
| 141 | + echo "Likely new famedly patch version $expected_new_version_tag" |
| 142 | + |
| 143 | +fi |
| 144 | + |
| 145 | +echo -e "\e[32m>>>> merging upstream into release branch\e[0m" |
| 146 | +# We let this error if there was a merge conflict that needs to be resolved. It should |
| 147 | +# be an idempotent process of just rerunning the script to continue on. After a |
| 148 | +# `git merge --continue`, of course |
| 149 | +set +e |
| 150 | +if ! git merge "upstream/release-${release_name}" -m "Famedly ${release_name}"; then |
| 151 | + echo "An error was detected. Fix the conflicts, 'git add' the changes, run 'git merge --continue' and re-run this script with the same arguments" |
| 152 | + exit 1 |
| 153 | +fi |
| 154 | +set -e |
| 155 | + |
| 156 | +# Retrieve the git log entries themselves and format them to fit well into the |
| 157 | +# markdown format. If there were none, we can skip this whole next section. This will |
| 158 | +# only pull the changes between the lastest tag found on this branch and master when the |
| 159 | +# tag is in our format and discards merges |
| 160 | +git_log_output=$(git log $(git describe --tags --abbrev=0 --match v*.*.*_*)..master --pretty=format:'- %s %C(bold blue)(%an)%Creset\' --no-merges --no-decorate) |
| 161 | +if [[ ! -z "$git_log_output" ]]; then |
| 162 | + echo -e "\n" |
| 163 | + echo "Note: Entries to be added to the CHANGES.md file are detected. I can do this automatically for you." |
| 164 | + echo -e "\e[31m WARNING: if you need to run this script multiple times, only do this ONCE!\e[0m" |
| 165 | + read -n 1 -p "Process Famedly change log entries, press 'y' or Enter to skip: " input_key |
| 166 | + echo -e "\n" |
| 167 | + if [[ "$input_key" == "y" ]]; then |
| 168 | + echo -e "\e[32m>>>> Adding Famedly changes to CHANGES.md\e[0m" |
| 169 | + echo -e "\e[32m>>>> ONLY RUN THIS ONE TIME\e[0m" |
| 170 | + # Find out how many lines down in CHANGES.md that the previous '# Synapse' entry |
| 171 | + # was. This gives us the line number needed to run the insertion. |
| 172 | + changelog_line_number=$(grep -n "# Synapse" CHANGES.md | awk 'NR==2 {print $1}' FS=":") |
| 173 | + |
| 174 | + # Shell expansion is stupid when it comes to escaping things. This is a hack to get |
| 175 | + # the new line character into a string that sed will actually interpret that does |
| 176 | + # not interfere with already appended '\' from the git log output. Used at the end |
| 177 | + # to give us the extra line inside the markdown. |
| 178 | + NL=$'\n' |
| 179 | + # Construct the sed command. This is a little messy. sed requires that new lines are |
| 180 | + # a '\' and not a normal ansi '\n'(and it will insert that for us). We need a basic |
| 181 | + # header for the entry and an additional new line at the end(the markdown itself |
| 182 | + # does not care but this makes it easier to find when opening the file directly). |
| 183 | + sed_command="${changelog_line_number} i\### Famedly additions for ${expected_new_version_tag}\n\n" |
| 184 | + sed_command+="${git_log_output}${NL}" |
| 185 | + |
| 186 | + # That should do it. The changes from our repo that occurred since the last upstream |
| 187 | + # merge(specifically that do not include said merge) should be inserted just above |
| 188 | + # that previous upstream merge(and just underneath the merge we are in the middle |
| 189 | + # of). |
| 190 | + sed -i "${sed_command}" CHANGES.md |
| 191 | + |
| 192 | + # Amend the last commit(which should be the merge commit itself) to incorporate |
| 193 | + # changes from the changelog just produced |
| 194 | + echo -e "\e[32m>>>> Amending last commit with Famedly changelog entries\e[0m" |
| 195 | + git add CHANGES.md |
| 196 | + git commit --amend --no-edit --allow-empty |
| 197 | + fi |
| 198 | +fi |
| 199 | + |
| 200 | +read -n 1 -p "Press 't' to run linting and tests, or any other key to skip: " input_key |
| 201 | +echo -e "\n" |
| 202 | +if [[ $input_key == "t" ]]; then |
| 203 | + echo -e "\e[32m>>>> running lint and tests...\e[0m" |
| 204 | + # Make sure there are no weirdities around poetry. Until the deprecation migration |
| 205 | + # occurs, expect orange things to read here. |
| 206 | + poetry check |
| 207 | + poetry install --extras all --no-interaction |
| 208 | + poetry run ./scripts-dev/lint.sh |
| 209 | + |
| 210 | + logical_cores=$([ $(uname) = 'Darwin' ] && |
| 211 | + sysctl -n hw.logicalcpu_max || |
| 212 | + nproc) |
| 213 | + |
| 214 | + poetry run trial -j"${logical_cores}" tests |
| 215 | + echo -e "\e[32m>>>> Testing successful!\e[0m" |
| 216 | +fi |
| 217 | + |
| 218 | +read -n 1 -p "Press 'p' to push branch to Github, or any other key to skip: " input_key |
| 219 | +echo -e "\n" |
| 220 | +if [[ $input_key == "p" ]]; then |
| 221 | + echo -e "\e[32m>>>> pushing release branch\e[0m" |
| 222 | + git push --force -u origin "${release_branch_name}" |
| 223 | + echo "Visit online `https://github.com/famedly/synapse/pull/new/${release_branch_name}`" |
| 224 | + echo "to open a pull request for this release." |
| 225 | +else |
| 226 | + echo "Not pushing branch. Run 'git push --force -u origin ${release_branch_name}' when ready" |
| 227 | +fi |
| 228 | + |
| 229 | +echo -e "\e[32m>>>> Finished!\e[0m" |
| 230 | +echo -e "\e[32m>> Recommend using $expected_new_version_tag as new release_name when running make_release.sh" |
0 commit comments