| #!/bin/bash | |
| # | |
| # Copyright 2017-present The Material Motion and Material Components for | |
| # iOS Authors. All Rights Reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # Abort if any command returns an error | |
| set -e | |
| parentcmd=$(basename "${BASH_SOURCE[1]}") | |
| cmd=$(basename "${BASH_SOURCE[0]}") | |
| dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" | |
| usage() { | |
| "$dir/readme_to_console" "$dir/README-release.md" | |
| } | |
| # HELPER METHODS | |
| current_branch() { | |
| git rev-parse --abbrev-ref HEAD | |
| } | |
| enforce_clean_state() { | |
| if [[ $(git status --porcelain) ]]; then | |
| echo "${B}Your git repo is not in a clean state.${N}" | |
| echo "Please revert or commit all changes before cutting a release." | |
| git status | |
| exit 1 | |
| fi | |
| } | |
| enforce_release_cut() { | |
| if [ ! $(git rev-parse --verify release-candidate 2> /dev/null) ]; then | |
| echo "No release in progress." | |
| exit 1 | |
| fi | |
| } | |
| enforce_changelog_version() { | |
| changelog_version=$(awk '/# (.+)/ { print $2; exit}' "$rootdir/CHANGELOG.md") | |
| changelog_version=$(version_for_platform $changelog_version) | |
| if [ "$changelog_version" == "#develop#" ]; then | |
| echo "${B}You haven't updated CHANGELOG.md with the current version yet.${N}" | |
| echo "Please run '$parentcmd $cmd bump [version]' before running this command again." | |
| exit 1 | |
| elif [ "$version" != "$changelog_version" ]; then | |
| echo "Mismatch in CHANGELOG.md's latest version." | |
| echo | |
| echo " CHANGELOG.md latest version: $changelog_version" | |
| echo " Desired version: $version" | |
| echo | |
| echo "Please edit CHANGELOG.md or change your version number." | |
| echo | |
| exit 1 | |
| fi | |
| } | |
| get_latest_branch() { | |
| if [ ! $(git rev-parse --verify release-candidate 2> /dev/null) ]; then | |
| echo "HEAD" | |
| else | |
| echo "release-candidate" | |
| fi | |
| } | |
| version_for_platform() { | |
| user_version="$1" | |
| if [ -f "$rootdir/Podfile" ]; then | |
| echo "v${user_version#v}" | |
| elif [ -f "$rootdir/build.gradle" ]; then | |
| echo "${user_version#v}" | |
| else | |
| echo "v${user_version#v}" | |
| fi | |
| } | |
| # COMMAND METHODS | |
| # Creates a release-candidate branch based off the latest origin/develop | |
| cut_release() { | |
| isHotFix=false; | |
| while test $# -gt 0; do | |
| case "$1" in | |
| --hotfix) | |
| isHotFix=true; | |
| shift | |
| ;; | |
| esac | |
| done | |
| if [ $(git rev-parse --verify release-candidate 2> /dev/null) ]; then | |
| echo "${B}Release already cut.${N}" | |
| echo "Consider deleting your existing release-candidate branch." | |
| exit 1 | |
| fi | |
| git fetch | |
| git show develop >> /dev/null 2>&1 || { git checkout -b develop origin/develop; } | |
| deviance=$(git log develop..origin/develop --oneline | wc -l) | |
| if [ $deviance -ne 0 ]; then | |
| echo | |
| echo " Your local develop branch is behind origin/develop." | |
| echo " Refusing to continue until you've rebased off of origin/develop." | |
| echo | |
| echo " git checkout develop" | |
| echo " git rebase origin/develop" | |
| echo | |
| exit 1 | |
| fi | |
| deviance=$(git log origin/develop..develop --oneline | wc -l) | |
| if [ $deviance -ne "0" ]; then | |
| echo | |
| echo " Your local develop branch is ahead of origin/develop." | |
| echo " Refusing to continue until you've landed your local changes into origin/develop." | |
| echo | |
| exit 1 # TODO: Revert this line so we bail out. | |
| fi | |
| if $isHotFix; then | |
| branch=origin/stable | |
| branch_message="This is a hotfix... branching off $branch" | |
| else | |
| branch=origin/develop | |
| branch_message="This is a normal release... branching off $branch" | |
| fi | |
| echo $branch_message | |
| git checkout -b release-candidate $branch | |
| git checkout origin/stable -- .gitattributes | |
| touch "$rootdir/CHANGELOG.md" | |
| if ! grep "# #develop#" "$rootdir/CHANGELOG.md" >> /dev/null; then | |
| echo "Generating API diff..." | |
| CHANGELOG_TMP_PATH=$(mktemp -d) | |
| generate_release_apidiff | tee "$CHANGELOG_TMP_PATH/api_diff" | |
| CHANGELOG_PATH=$(cat "$CHANGELOG_TMP_PATH/api_diff" | grep "Changelog=" | cut -d'=' -f2) | |
| # Add the new changelog contents in reverse order: | |
| echo -e "\n---\n" | cat - "$rootdir/CHANGELOG.md" > /tmp/out && mv /tmp/out "$rootdir/CHANGELOG.md" | |
| generate_release_notes | cat - "$rootdir/CHANGELOG.md" > /tmp/out && mv /tmp/out "$rootdir/CHANGELOG.md" | |
| echo -e "\n## Component changes" | cat - "$rootdir/CHANGELOG.md" > /tmp/out && mv /tmp/out "$rootdir/CHANGELOG.md" | |
| cat "$CHANGELOG_PATH" | cat - "$rootdir/CHANGELOG.md" > /tmp/out && mv /tmp/out "$rootdir/CHANGELOG.md" | |
| echo -e "## API changes" | cat - "$rootdir/CHANGELOG.md" > /tmp/out && mv /tmp/out "$rootdir/CHANGELOG.md" | |
| echo -e "Replace this text with example code for each new feature." | cat - "$rootdir/CHANGELOG.md" > /tmp/out && mv /tmp/out "$rootdir/CHANGELOG.md" | |
| echo -e "## New features\n" | cat - "$rootdir/CHANGELOG.md" > /tmp/out && mv /tmp/out "$rootdir/CHANGELOG.md" | |
| echo -e "Replace this text with links to deprecation guides." | cat - "$rootdir/CHANGELOG.md" > /tmp/out && mv /tmp/out "$rootdir/CHANGELOG.md" | |
| echo -e "## New deprecations\n" | cat - "$rootdir/CHANGELOG.md" > /tmp/out && mv /tmp/out "$rootdir/CHANGELOG.md" | |
| echo -e "Replace this explanations for how to resolve the breaking changes." | cat - "$rootdir/CHANGELOG.md" > /tmp/out && mv /tmp/out "$rootdir/CHANGELOG.md" | |
| echo -e "## Breaking changes\n" | cat - "$rootdir/CHANGELOG.md" > /tmp/out && mv /tmp/out "$rootdir/CHANGELOG.md" | |
| echo -e "Replace this text with a summarized description of this release's contents." | cat - "$rootdir/CHANGELOG.md" > /tmp/out && mv /tmp/out "$rootdir/CHANGELOG.md" | |
| echo -e "# #develop#\n" | cat - "$rootdir/CHANGELOG.md" > /tmp/out && mv /tmp/out "$rootdir/CHANGELOG.md" | |
| fi | |
| git add "$rootdir/CHANGELOG.md" | |
| git commit -m "Automatic changelog preparation for release." | |
| git push origin release-candidate -u | |
| RELEASE_SHA=$(git merge-base --fork-point release-candidate $branch) | |
| PULL_REQUEST_URL="https://github.com/material-components/material-components-ios/compare/stable...release-candidate" | |
| echo "${B}You can now start the release-candidate pull request:${N}" | |
| echo | |
| echo " $PULL_REQUEST_URL" | |
| echo | |
| echo "This will initiate public testing of the release candidate." | |
| echo | |
| echo "${B}You can now kick off internal testing.${N}" | |
| } | |
| abort_release() { | |
| enforce_release_cut | |
| echo "${B}About to abort the release candidate.${N}" | |
| echo "${B}${U}This action is not easily reversible.${N}" | |
| echo | |
| echo -n "Press enter to continue..." | |
| read | |
| git checkout origin/develop | |
| git branch -D release-candidate | |
| git push origin :release-candidate | |
| } | |
| test_release() { | |
| "$dir/prep_all" | |
| "$dir/build_all" --verbose | |
| "$dir/test_all" | |
| "$dir/build_codelabs" | |
| } | |
| bump_release() { | |
| if [ -z "$1" ]; then | |
| echo "Missing desired version." | |
| echo | |
| echo "Usage: $parentcmd $cmd bump <desired version> [<old version>]" | |
| echo | |
| exit 1 | |
| fi | |
| enforce_release_cut | |
| new_version="$1" | |
| new_version=${new_version#v} | |
| if [ -z "$2" ]; then | |
| last_version=$(git describe --tags $(git rev-list --tags --max-count=1)) | |
| else | |
| last_version="$2" | |
| fi | |
| last_version=${last_version#v} | |
| grep -FIlr "$last_version" . 2>/dev/null | grep -v -f "$dir/versionignore" | while read line; do | |
| sed -i.bak "s:$last_version:$new_version:g" "$line" | |
| rm "$line.bak" | |
| done | |
| grep -FIlr "#develop#" . 2>/dev/null | grep -v -f "$dir/versionignore" | while read line; do | |
| sed -i.bak "s:#develop#:$new_version:g" "$line" | |
| rm "$line.bak" | |
| done | |
| } | |
| merge_release() { | |
| enforce_release_cut | |
| version=$(version_for_platform $1) | |
| if [ -z "$version" ]; then | |
| echo "Must provide a ${U}version${N} argument." | |
| exit 1 | |
| fi | |
| deviance=$(git log develop..origin/develop --oneline | wc -l) | |
| if [ $deviance -ne 0 ]; then | |
| echo | |
| echo " Your local develop branch is behind origin/develop." | |
| echo " Refusing to continue until you've rebased off of origin/develop." | |
| echo | |
| echo " git checkout develop" | |
| echo " git rebase origin/develop" | |
| echo | |
| exit 1 | |
| fi | |
| deviance=$(git log origin/develop..develop --oneline | wc -l) | |
| if [ $deviance -ne "0" ]; then | |
| echo | |
| echo " Your local develop branch is ahead of origin/develop." | |
| echo " Refusing to continue until you've landed your local changes into origin/develop." | |
| echo | |
| exit 1 # TODO: Revert this line so we bail out. | |
| fi | |
| current_branch=$(current_branch) | |
| if [ "$current_branch" != "release-candidate" ]; then | |
| echo "Checking out the release-candidate branch..." | |
| git checkout release-candidate | |
| fi | |
| enforce_changelog_version | |
| if [ $(git rev-list --tags --max-count=1 2> /dev/null) ]; then | |
| last_version=$(git describe --tags $(git rev-list --tags --max-count=1)) | |
| last_version=${last_version#v} | |
| last_version=$(echo "$last_version" | sed "s:\.:\\\\.:g") | |
| if grep -Ilr "$last_version" . | grep -v -f "$dir/versionignore"; then | |
| echo "Old version $last_version found in the files above." | |
| read -r -p "Continue? [y/N] " response | |
| case $response in | |
| [yY][eE][sS]|[yY]) ;; | |
| *) | |
| echo "Aborting release merge. Please run $parentcmd $cmd bump" | |
| exit 1 | |
| ;; | |
| esac | |
| fi | |
| fi | |
| echo "Merging release-candidate into stable and develop..." | |
| # Legend of commits. | |
| # Each numbered commit corresponds to a set of commands below, with all `o` symbols having been | |
| # committed by earlier steps in the release process. | |
| # | |
| # develop o-o----------3 | |
| # \ / | |
| # release-candidate o--o---2 | |
| # \ / | |
| # stable o--------1 | |
| # Commit #1: Create the releasable merge commit in stable. | |
| git fetch | |
| if [ ! $(git rev-parse --verify stable 2> /dev/null) ]; then | |
| git checkout -b stable origin/stable | |
| else | |
| git checkout stable | |
| fi | |
| git rebase origin/stable | |
| git merge --no-ff release-candidate --no-edit | |
| # Commit #2: Connect stable to develop's ancestry by merging stable back into the | |
| # release-candidate. | |
| git checkout release-candidate | |
| git merge --no-ff stable --no-edit | |
| # Commit #3: Merge the release-candidate into develop. | |
| if [ ! $(git rev-parse --verify develop 2> /dev/null) ]; then | |
| git checkout -b develop origin/develop | |
| else | |
| git checkout develop | |
| fi | |
| git rebase origin/develop | |
| # The following lines will only create one commit thanks to the --no-commit argument here. | |
| git merge --no-ff --no-commit release-candidate --no-edit | |
| git reset HEAD .gitattributes | |
| git checkout -- .gitattributes | |
| git commit -m "merged release-candidate" | |
| git branch -D release-candidate | |
| git checkout stable | |
| } | |
| publish_release() { | |
| version=$(version_for_platform $1) | |
| if [ -z "$version" ]; then | |
| echo "Must provide a ${U}version${N} argument." | |
| exit 1 | |
| fi | |
| current_branch=$(current_branch) | |
| if [ "$current_branch" != "stable" ]; then | |
| echo "This command must be run from the ${B}stable${N} branch." | |
| echo | |
| echo "Your current branch: $current_branch" | |
| exit 1 | |
| fi | |
| enforce_changelog_version | |
| ghtoken="$(cut -d " " -f 2 <<< $(cat ~/.config/gh/hosts.yml | grep "oauth_token: "))" | |
| if [ -z "$ghtoken" ]; then | |
| echo "Error: Unable to find a token in ~/.config/gh/hosts.yml." | |
| echo | |
| echo "You must authenticate with the official GitHub command line interface tool:" | |
| echo "https://github.com/cli/cli" | |
| echo | |
| echo "Not to be confused with the following unofficial (and no longer maintained) GitHub command line tool that this script used to use:" | |
| echo "https://github.com/node-gh/gh" | |
| echo | |
| echo "To proceed, do the following:" | |
| echo "1. Install the official command line tool" | |
| echo "2. Authenticate using 'gh auth login'" | |
| echo " a. When it asks 'What account do you want to log into?', choose 'GitHub.com'" | |
| echo " b. When it asks 'How would you like to authenticate?', choose 'Login with a web browser'" | |
| echo "3. Re-run the scripts/release command" | |
| echo | |
| echo "You will probably have to delete or move the old gh to make way for the new (official) one." | |
| exit 1 | |
| fi | |
| curl -sH "Authorization: token $ghtoken" \ | |
| "https://api.github.com/repos/material-components/material-components-ios/releases/tags/$version" \ | |
| | grep -q 'message": "Not Found' | |
| if [ $? -ne 0 ]; then # Found the release | |
| echo "Release already cut." | |
| echo | |
| echo " Release URL: https://github.com/material-components/material-components-ios/releases/tag/$version" | |
| open "https://github.com/material-components/material-components-ios/releases/tag/$version" | |
| exit 0 | |
| fi | |
| git push origin stable develop | |
| tmp_path=$(mktemp -d) | |
| curl -s \ | |
| -H "Authorization: token $ghtoken" \ | |
| -H "Content-Type: application/json" \ | |
| -X POST \ | |
| -d '{"tag_name":"'$version'","target_commitish":"stable","name":"'$version'","draft":true}' \ | |
| "https://api.github.com/repos/material-components/material-components-ios/releases" > "$tmp_path/release" | |
| if [ $? -ne 0 ]; then # Found the release | |
| echo "Failed to draft the release. Check that it doesn't already exist before continuing." | |
| echo "https://api.github.com/repos/material-components/material-components-ios/releases" | |
| exit 1 | |
| fi | |
| htmlurl=$(cat "$tmp_path/release" | grep '^ "html_url' | cut -d'"' -f4) | |
| htmlurl=$(echo $htmlurl | sed "s:/tag/:/edit/:") | |
| echo "A draft release has been made." | |
| echo | |
| echo " Edit the draft: $htmlurl" | |
| echo | |
| echo "Update the release's description with the following:${B}" | |
| awk '/# / { print $0; while(getline > 0) {if (/^# /) exit; print $0 }}' "$rootdir/CHANGELOG.md" | tail -n +2 | |
| echo ${N} | |
| echo "Deleting remote release-candidate..." | |
| git push origin :release-candidate | |
| git checkout develop | |
| echo "Press enter to open the release draft url in your browser:" | |
| read | |
| if [ "$(uname)" == "Darwin" ]; then | |
| open $htmlurl | |
| elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then | |
| xdg-open $htmlurl | |
| fi | |
| } | |
| publish_podspec() { | |
| cat <<EOL | |
| |\\___/| | |
| (,\\ /,)\\ | |
| / / \\ DRAGON SAYS HALT: | |
| (@_^_@)/ \\ HAS THE INTERNAL CL LANDED? | |
| W//W_/ \\ DO NOT PUBLISH THIS RELEASE UNTIL | |
| (//) | \\ IT HAS LANDED. | |
| (/ /) _|_ / ) \\ | |
| (// /) '/,_ _ _/ (~^-. | |
| (( // )) ,-{ _ \`. | |
| (( /// )) '/\\ / \\ | |
| (( ///)) \`. { } \\ | |
| ((/ )) .----~-.\\ \\-' ~-__ | |
| ///.----..> \\ \\_ ~--____ | |
| ///-._ _ _ _} ~-------------- | |
| EOL | |
| echo "Press enter to confirm that the internal CL has landed." | |
| read | |
| cd "$rootdir" | |
| echo "Verifying Cocoapod trunk permissions" | |
| pod trunk me > cocoapods_permissions.log | |
| if grep -Fq "MaterialComponents" cocoapods_permissions.log; then | |
| echo "Running a pod trunk push from stable..." | |
| git checkout stable | |
| pod trunk push MaterialComponents.podspec | |
| rm cocoapods_permissions.log | |
| else | |
| echo "You do not have the right permissions to push the pod via the CocoaPods API. See cocoapods_permissions.log for more info." | |
| fi | |
| } | |
| # Generators | |
| generate_release_apidiff() { | |
| CHANGELOG_TMP_PATH=$(mktemp -d) | |
| validate_commit() { | |
| git cat-file -t $1 >> /dev/null 2> /dev/null || { echo "$1 is not a valid commit."; exit 1; } | |
| } | |
| # Verify commits | |
| if [ -n "$1" ]; then | |
| old_commit=$(git rev-list -n 1 $1) | |
| else | |
| old_commit=$(git rev-list -n 1 origin/stable) | |
| fi | |
| new_commit=$(git rev-list -n 1 $(get_latest_branch)) | |
| if [[ -z "$old_commit" || -z "$new_commit" ]]; then | |
| echo "Unable to get commit shas." | |
| exit 1 | |
| fi | |
| validate_commit $old_commit | |
| validate_commit $new_commit | |
| TMP_PATH=$(mktemp -d) | |
| OLD_ROOT_PATH="$TMP_PATH/old" | |
| NEW_ROOT_PATH="$TMP_PATH/new" | |
| clean_clones() { | |
| if [ ! -z "$OLD_ROOT_PATH" ]; then | |
| rm -rf "$OLD_ROOT_PATH" | |
| fi | |
| if [ ! -z "$NEW_ROOT_PATH" ]; then | |
| rm -rf "$NEW_ROOT_PATH" | |
| fi | |
| } | |
| trap clean_clones EXIT | |
| # We do not need LFS for the temporary clones; they are used solely to do API diffs. | |
| # See https://github.com/git-lfs/git-lfs/issues/2406 for where this flag came from. | |
| export GIT_LFS_SKIP_SMUDGE=1 | |
| "$dir/temporary_clone_at_ref" "$OLD_ROOT_PATH" $old_commit | |
| "$dir/temporary_clone_at_ref" "$NEW_ROOT_PATH" $new_commit | |
| unset GIT_LFS_SKIP_SMUDGE | |
| # Find command in all component src directories and grab search path for "Material$component.h" | |
| old_header_search_paths="" | |
| new_header_search_paths="" | |
| for d in $NEW_ROOT_PATH/components/*/src; do | |
| folder=$(dirname $d) | |
| component=$(basename $folder) | |
| old_header_search_paths="$old_header_search_paths --oldargs -I$OLD_ROOT_PATH/components/$component/src/ " | |
| new_header_search_paths="$new_header_search_paths --newargs -I$NEW_ROOT_PATH/components/$component/src/ " | |
| done | |
| if [ ! -f "$dir/external/material-motion-apidiff/src/pathapidiff" ]; then | |
| git submodule update --init --recursive | |
| fi | |
| ALL_CHANGELOG_PATH="$TMP_PATH/changelog" | |
| ALL_ERROR_LOG_PATH="$TMP_PATH/errlog" | |
| echo "Changelog=$ALL_CHANGELOG_PATH" | |
| echo "Errors=$ALL_ERROR_LOG_PATH" | |
| # Run new pathdiff script on each umbrella header in array | |
| for umbrella_header_path in $(generate_release_umbrella_headers "$old_commit" | grep -v "private"); do | |
| umbrella_header=$(basename "$umbrella_header_path") | |
| component=$(echo "$umbrella_header" | cut -d'.' -f1 | sed 's:^Material::') | |
| component_path=$(dirname "$umbrella_header_path") | |
| echo -n "Diffing $component ($component_path)..." | |
| if [ ! -f "$OLD_ROOT_PATH/$umbrella_header_path" ]; then | |
| echo >> $ALL_CHANGELOG_PATH | |
| echo "### $component" >> $ALL_CHANGELOG_PATH | |
| echo >> $ALL_CHANGELOG_PATH | |
| if [[ "$component" = *"+"* ]]; then | |
| echo "**New extension.**" >> $ALL_CHANGELOG_PATH | |
| else | |
| echo "**New component.**" >> $ALL_CHANGELOG_PATH | |
| fi | |
| echo "New!" | |
| continue | |
| fi | |
| CHANGES_PATH="$TMP_PATH/${component}changes" | |
| ERROR_PATH="$TMP_PATH/${component}errlog" | |
| "$dir/external/material-motion-apidiff/src/pathapidiff" \ | |
| "$OLD_ROOT_PATH" "$NEW_ROOT_PATH" objc "/$umbrella_header_path" \ | |
| >> "$CHANGES_PATH" \ | |
| 2>> "$ERROR_PATH" | |
| if [ -s "$CHANGES_PATH" ]; then | |
| echo >> $ALL_CHANGELOG_PATH | |
| echo "### $component" >> $ALL_CHANGELOG_PATH | |
| cat "$CHANGES_PATH" >> $ALL_CHANGELOG_PATH | |
| echo -n " Changes detected." | |
| fi | |
| if [ -s "$ERROR_PATH" ]; then | |
| echo "### $component" >> "$ALL_ERROR_LOG_PATH" | |
| cat "$ERROR_PATH" >> "$ALL_ERROR_LOG_PATH" | |
| fi | |
| echo | |
| done | |
| } | |
| generate_release_authors() { | |
| git log origin/stable...$(get_latest_branch) --format="%ae" | sort | uniq | |
| } | |
| generate_release_components() { | |
| if [ -n "$1" ]; then | |
| old_commit="$1" | |
| else | |
| old_commit=origin/stable | |
| fi | |
| git diff --name-only "$old_commit"..$(get_latest_branch) components/ \ | |
| | grep "src/" | cut -d'/' -f2- | rev | cut -d'/' -f3- | rev | sed 's|/src||' | sort | uniq | |
| } | |
| generate_release_files() { | |
| if [ -n "$1" ]; then | |
| old_commit="$1" | |
| else | |
| old_commit=origin/stable | |
| fi | |
| git diff --name-only "$old_commit"..$(get_latest_branch) components/ | |
| } | |
| generate_release_umbrella_headers() { | |
| for file in $(generate_release_files "$@"); do | |
| file_dir=$(dirname $file) | |
| if ls "$file_dir"/Material*.h 1> /dev/null 2>&1; then | |
| ls "$file_dir"/Material*.h | grep -v "_table" | |
| fi | |
| done | sort | uniq | |
| } | |
| generate_release_headers() { | |
| git diff --name-only origin/stable..$(get_latest_branch) components/ \ | |
| | grep -i -e "components\/.*\/src\/.*\.h" \ | |
| | grep -v -i -e "\/private\/" | |
| } | |
| generate_release_log() { | |
| git --no-pager log origin/stable..$(get_latest_branch) "$@" | |
| } | |
| generate_release_diff() { | |
| git_diff=diff | |
| if [ "$1" == "--use_diff_tool" ]; then | |
| git_diff=difftool | |
| shift 1 | |
| fi | |
| git $git_diff origin/stable..$(get_latest_branch) "$@" | |
| } | |
| generate_release_notes() { | |
| # Echoes the SHA's title as a CHANGELOG.md entry. | |
| sha_to_changelog_entry() { | |
| sha="$1" | |
| git log \ | |
| -1 \ | |
| --pretty="* [%s](https://github.com/material-components/material-components-ios/commit/%H) (%an)" \ | |
| --no-merges \ | |
| "$sha" | |
| } | |
| # Returns a non-zero value if the given SHA affects any paths other than the given path. | |
| # Ignores modifications to MaterialComponents.podspec. | |
| does_commit_affect_other_paths() { | |
| sha="$1" | |
| path="$2" | |
| git diff-tree --no-commit-id --name-only -r "$sha" \ | |
| | grep -v "$path" \ | |
| | grep -v "MaterialComponents.podspec" \ | |
| | grep -v "MaterialComponentsBeta.podspec" \ | |
| | grep -v "catalog/Podfile" \ | |
| | grep -v "snapshot_test_goldens" | |
| } | |
| # Echoes a list of changelog entries that affect a given path and only that path. | |
| changes_for_path() { | |
| path="${1:-components/}" | |
| git log \ | |
| --pretty="%H" \ | |
| --no-merges \ | |
| origin/stable..$(get_latest_branch) "$path" | while read sha; do | |
| if [[ ! $(does_commit_affect_other_paths "$sha" "$path") ]]; then | |
| sha_to_changelog_entry "$sha" | |
| fi | |
| done | |
| } | |
| # Echoes a list of changes that affect the given path AND other paths. | |
| changes_for_multiple_paths() { | |
| path="${1:-components/}" | |
| git log \ | |
| --pretty="%H" \ | |
| --no-merges \ | |
| origin/stable..$(get_latest_branch) "$path" | while read sha; do | |
| if [[ $(does_commit_affect_other_paths "$sha" "$path") ]]; then | |
| sha_to_changelog_entry "$sha" | |
| fi | |
| done | |
| } | |
| # Echoes a list of breaking changelog entries for a given path. | |
| filter_breaking_changes() { | |
| cat - | grep "\[.*\]\!" | perl -pe "s|\* \[\[.+?\]!|* [**Breaking**:|" | sort | |
| } | |
| # Echoes a list of non-breaking changelog entries for a given path. | |
| filter_non_breaking_changes() { | |
| cat - | grep -v "\[.*\]\!" | perl -pe "s|\* \[\[.+?\] |* [|" | sort | |
| } | |
| has_breaking_changes=false | |
| has_non_breaking_changes=false | |
| # Output breaking changes that affect specific components. | |
| find components -type d -name 'src' | sort | while read path; do | |
| folder=$(dirname $path) | |
| if [[ $(changes_for_path "$folder" | filter_breaking_changes) ]]; then | |
| component=$(echo $folder | cut -d'/' -f2-) | |
| if [ "$has_breaking_changes" = false ]; then | |
| has_breaking_changes=true | |
| echo | |
| echo "## Breaking changes" | |
| fi | |
| echo | |
| echo "### $component" | |
| echo | |
| changes_for_path "$folder" | filter_breaking_changes | |
| fi | |
| done | |
| # Output breaking changes that affect multiple components. | |
| all_breaking_changes_for_multiple_paths() { | |
| find components -type d -name 'src' | while read path; do | |
| folder=$(dirname $path) | |
| changes_for_multiple_paths "$folder" | filter_breaking_changes | |
| done | sort | uniq | |
| } | |
| if [[ $(all_breaking_changes_for_multiple_paths) ]]; then | |
| has_breaking_changes=true | |
| echo | |
| echo "## Multi-component breaking changes" | |
| echo | |
| all_breaking_changes_for_multiple_paths | |
| fi | |
| # Output changes that affect specific components. | |
| find components -type d -name 'src' | sort | while read path; do | |
| folder=$(dirname $path) | |
| if [[ $(changes_for_path "$folder" | filter_non_breaking_changes) ]]; then | |
| component=$(echo $folder | cut -d'/' -f2-) | |
| echo | |
| echo "### $component" | |
| echo | |
| changes_for_path "$folder" | filter_non_breaking_changes | |
| fi | |
| done | |
| # Output changes that affect multiple components. | |
| all_non_breaking_changes_for_multiple_paths() { | |
| find components -type d -name 'src' | while read path; do | |
| folder=$(dirname $path) | |
| changes_for_multiple_paths "$folder" | filter_non_breaking_changes | |
| done | sort | uniq | |
| } | |
| if [[ $(all_non_breaking_changes_for_multiple_paths) ]]; then | |
| has_non_breaking_changes=true | |
| echo | |
| echo "## Multi-component changes" | |
| echo | |
| all_non_breaking_changes_for_multiple_paths | |
| fi | |
| } | |
| other_thing() { | |
| find components -type d -name 'src' | sort | while read path; do | |
| folder=$(dirname $path) | |
| component=$(echo $folder | cut -d'/' -f2-) | |
| if [[ $component == private* ]]; then | |
| continue; | |
| fi | |
| if [ $(git log --pretty=oneline --no-merges origin/stable..$(get_latest_branch) $folder \ | |
| | wc -l) == "0" ]; then | |
| continue | |
| fi | |
| componentdiff() { | |
| git log \ | |
| --pretty="* [%s](https://github.com/material-components/material-components-ios/commit/%H) (%an)" \ | |
| --no-merges \ | |
| origin/stable..$(get_latest_branch) \ | |
| $folder | grep -v "\[automated\]" | |
| } | |
| if [[ $(componentdiff) ]]; then | |
| echo | |
| echo "### $component" | |
| if [[ $(componentdiff | grep "\[$component\]\!") ]]; then | |
| echo | |
| echo "#### Breaking changes" | |
| echo | |
| componentdiff \ | |
| | grep "\[$component\]\!" \ | |
| | sed "s|\[$component\]!|**Breaking**: |" \ | |
| | sort | |
| fi | |
| if [[ $(componentdiff | grep -v "\[$component\]\!") ]]; then | |
| echo | |
| componentdiff \ | |
| | grep -v "\[$component\]!" \ | |
| | sed "s|\[$component\] ||" \ | |
| | sort | |
| fi | |
| fi | |
| done | |
| } | |
| generate_release_source() { | |
| git diff --name-only origin/stable..$(get_latest_branch) components/ \ | |
| | grep "src/" | |
| } | |
| generate_release_stories() { | |
| generate_release_log | grep -i pivotal | |
| } | |
| if [ $# -eq 0 ]; then | |
| usage | |
| exit 1 | |
| fi | |
| if [ ! $(git rev-parse --is-inside-work-tree -q 2> /dev/null) ]; then | |
| echo "Must be run from a git directory." | |
| exit 1 | |
| fi | |
| if [ -t 1 ] ; then # We're writing directly to terminal | |
| readonly B=$(tput bold) | |
| readonly U=$(tput smul) | |
| readonly N=$(tput sgr0) | |
| else # We're in a pipe | |
| readonly B="" | |
| readonly U="" | |
| readonly N="" | |
| fi | |
| rootdir=$( cd "$(dirname $(git rev-parse --git-dir))" && pwd ) | |
| #enforce_clean_state | |
| case "$1" in | |
| cut) cut_release ${@:2} ;; | |
| test) test_release ${@:2} ;; | |
| bump) bump_release ${@:2} ;; | |
| merge) merge_release ${@:2} ;; | |
| publish) publish_release ${@:2} ;; | |
| podspec) publish_podspec ${@:2} ;; | |
| apidiff) generate_release_apidiff ${@:2} ;; # args: [base sha] | |
| authors) generate_release_authors ${@:2} ;; | |
| components) generate_release_components ${@:2} ;; # args: [base sha] | |
| diff) generate_release_diff ${@:2} ;; | |
| files) generate_release_files ${@:2} ;; # args: [base sha] | |
| headers) generate_release_headers ${@:2} ;; | |
| log) generate_release_log ${@:2} ;; | |
| notes) generate_release_notes ${@:2} ;; | |
| source) generate_release_source ${@:2} ;; | |
| stories) generate_release_stories ${@:2} ;; | |
| umbrellas) generate_release_umbrella_headers ${@:2} ;; # args: [base sha] | |
| abort) abort_release ${@:2} ;; | |
| *) usage ;; | |
| esac |