Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve env variables #9

Merged
merged 4 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ sa:
ifndef STATIC_ANALYSIS_CHECKER
@printf "\e[1m\e[31m%s\e[0m\n" "Shellcheck not installed: Static analysis not performed!" && exit 1
else
@shellcheck ./**/*.sh -C && printf "\e[1m\e[32m%s\e[0m\n" "ShellCheck: OK!"
@shellcheck ./**/**/*.sh -C && printf "\e[1m\e[32m%s\e[0m\n" "ShellCheck: OK!"
endif

lint:
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@ Examples:

## Env variables

### RELEASE_SOURCE_BRANCH

The default source branch that you want to use for your releases.

> Default: `main`

### RELEASE_TARGET_BRANCH

The default target branch that you want to use for your releases.

> Default: `prod`

### RELEASE_DEVELOPMENT_BRANCH

If you have a different develop branch from the source branch, you can also define it here.

> Default: `main`

### RELEASE_SUCCESSFUL_TEXT

Display a text at the very end of the release.
Expand Down
36 changes: 16 additions & 20 deletions release
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,17 @@ source "$RELEASE_ROOT_DIR/src/colors.sh"
source "$RELEASE_ROOT_DIR/src/compare.sh"
source "$RELEASE_ROOT_DIR/src/console_header.sh"
source "$RELEASE_ROOT_DIR/src/env.sh"
source "$RELEASE_ROOT_DIR/src/git.sh"
source "$RELEASE_ROOT_DIR/src/io.sh"
source "$RELEASE_ROOT_DIR/src/release.sh"
source "$RELEASE_ROOT_DIR/src/validate.sh"
source "$RELEASE_ROOT_DIR/src/slack.sh"
source "$RELEASE_ROOT_DIR/src/json.sh"
source "$RELEASE_ROOT_DIR/src/main.sh"

# Check if at least one argument (branch name) is passed
if [ $# -lt 1 ]; then
console_header::print_help
exit 1
fi

SOURCE_BRANCH=${1:-SOURCE_BRANCH:-"main"}
TARGET_BRANCH=${TARGET_BRANCH:-"prod"}
DEVELOPMENT_BRANCH=${DEVELOPMENT_BRANCH:-"main"}
RELEASE_SUCCESSFUL_TEXT=${RELEASE_SUCCESSFUL_TEXT:-}
DRY_RUN=${DRY_RUN:-false}
DRY_RUN=false
FORCE_DEPLOY=false
GH_CLI_INSTALLED=false

while [[ $# -gt 0 ]]; do
argument="$1"
Expand All @@ -53,33 +45,37 @@ while [[ $# -gt 0 ]]; do
exit 0
;;
--source)
SOURCE_BRANCH="$2"
RELEASE_SOURCE_BRANCH="$2"
shift
;;
--target)
TARGET_BRANCH="$2"
RELEASE_TARGET_BRANCH="$2"
shift
;;
--develop)
DEVELOPMENT_BRANCH="$2"
RELEASE_DEVELOPMENT_BRANCH="$2"
shift
;;
*)
RELEASE_SOURCE_BRANCH=$argument
esac
shift
done

GH_CLI_INSTALLED=false
if command -v gh &> /dev/null; then
GH_CLI_INSTALLED=true
fi

export GH_CLI_INSTALLED
#######################################################

export DRY_RUN
export FORCE_DEPLOY
export GH_CLI_INSTALLED
export RELEASE_SOURCE_BRANCH
export RELEASE_TARGET_BRANCH
export RELEASE_DEVELOPMENT_BRANCH

main::action "$SOURCE_BRANCH" \
"$TARGET_BRANCH" \
"$DEVELOPMENT_BRANCH" \
"$FORCE_DEPLOY"
main::action "$FORCE_DEPLOY"

echo -e "${COLOR_GREEN}Script completed${COLOR_RESET}"
env::render_successful_text
10 changes: 10 additions & 0 deletions src/env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
[[ -f ".env" ]] && source .env set
set +o allexport

_DEFAULT_SOURCE_BRANCH="main"
_DEFAULT_TARGET_BRANCH="prod"
_DEFAULT_DEVELOPMENT_BRANCH="main"
_DEFAULT_SUCCESSFUL_TEXT=""

: "${RELEASE_SOURCE_BRANCH:=${SOURCE_BRANCH:=$_DEFAULT_SOURCE_BRANCH}}"
: "${RELEASE_TARGET_BRANCH:=${TARGET_BRANCH:=$_DEFAULT_TARGET_BRANCH}}"
: "${RELEASE_DEVELOPMENT_BRANCH:=${DEVELOPMENT_BRANCH:=$_DEFAULT_DEVELOPMENT_BRANCH}}"
: "${RELEASE_SUCCESSFUL_TEXT:=${SUCCESSFUL_TEXT:=$_DEFAULT_SUCCESSFUL_TEXT}}"

function env::run_extra_confirmation() {
local changed_files=$1

Expand Down
74 changes: 74 additions & 0 deletions src/git.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/bin/bash

function git::status() {
git status
}

function git::fetch_origin() {
git fetch origin
}

function git::changed_files() {
git diff --name-only "$1".."$2"
}

function git::latest_tag() {
git describe --tags "$(git rev-list --tags --max-count=1)" 2>/dev/null || echo "v0"
}

function git::force_checkout() {
local branch_name="${1#origin/}" # Remove 'origin/' prefix if present

if [[ "$DRY_RUN" == true ]]; then
echo -e "${COLOR_CYAN}--dry-run enabled. Skipping git fetch & checkout${COLOR_RESET}" \
"${COLOR_ORANGE}origin/$branch_name${COLOR_RESET}"
return
fi

git config advice.detachedHead false
[ -f .git/hooks/post-checkout ] && mv .git/hooks/post-checkout .git/hooks/post-checkout.bak

git fetch origin

if git rev-parse --verify "$branch_name" >/dev/null 2>&1; then
# If branch exists locally, force checkout it
git checkout -f "$branch_name"
else
# If branch doesn't exist, create a new local branch from the remote
git checkout -b "$branch_name" origin/"$branch_name"
fi

[ -f .git/hooks/post-checkout.bak ] && mv .git/hooks/post-checkout.bak .git/hooks/post-checkout
git config advice.detachedHead true
}

function git::update_develop() {
local develop=$1
local target=$2

echo -e "Merging ${COLOR_ORANGE}$target${COLOR_RESET} back to" \
"${COLOR_ORANGE}$develop${COLOR_RESET} (increase the release contains hotfixes" \
"that are not in ${COLOR_ORANGE}$develop${COLOR_RESET})"

git::force_checkout "$develop"
git::merge_source_to_target "$target" "$develop"
}

function git::merge_source_to_target() {
local source=$1
local target=$2

echo -e "Merging ${COLOR_ORANGE}$source${COLOR_RESET} release to ${COLOR_ORANGE}$target${COLOR_RESET}"

if [[ "$DRY_RUN" == true ]]; then
echo -e "${COLOR_CYAN}--dry-run enabled. Skipping git merge ($source into $target)${COLOR_RESET}"
return
fi

if ! git merge "$source"; then
echo -e "${COLOR_RED}Merge failed. Please resolve conflicts and try again.${COLOR_RESET}"
exit 1
fi

git push origin "$target" --no-verify
}
1 change: 0 additions & 1 deletion src/io.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/bin/bash
set -euo pipefail

# shellcheck disable=SC2155
function io::confirm_or_exit() {
Expand Down
85 changes: 14 additions & 71 deletions src/main.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
set -euo pipefail

function main::action() {
local source=${1:-main}
local target=${2:-prod}
local develop=${3:-1:-main}
local force_release=${4:-false}
local force_release=${1:-false}

local source=${RELEASE_SOURCE_BRANCH:-main}
local target=${RELEASE_TARGET_BRANCH:-prod}
local develop=${RELEASE_DEVELOPMENT_BRANCH:-$source}

validate::slack_configured "$force_release"

Expand All @@ -18,15 +19,15 @@ function main::action() {
main::render_steps "$source" "$target" "$develop"

echo -e "${COLOR_PURPLE}------------------------------------------------------------------${COLOR_RESET}"
git fetch origin
git status
git::fetch_origin
git::status
echo -e "${COLOR_BLUE}------------------------------------------------------------------${COLOR_RESET}"

echo -e "Using source branch: ${COLOR_ORANGE}$source${COLOR_RESET}"
validate::no_diff_between_local_and_origin "$source" "$target" "$force_release"

local changed_files=$(git diff --name-only "$target".."$source")
local latest_tag=$(git describe --tags "$(git rev-list --tags --max-count=1)" 2>/dev/null || echo "v0")
local changed_files=$(git::changed_files "$target" "$source")
local latest_tag=$(git::latest_tag)

echo -e "Current latest tag: ${COLOR_CYAN}$latest_tag${COLOR_RESET}"
local new_tag=$(release::generate_new_tag "$latest_tag" "$changed_files")
Expand All @@ -45,19 +46,19 @@ function main::action() {
env::run_extra_confirmation "$changed_files"
env::run_extra_commands "$changed_files"

main::force_checkout "$target"
main::merge_source_to_target "$source" "$target"
git::force_checkout "$target"
git::merge_source_to_target "$source" "$target"

release::create_tag "$target" "$new_tag" "$changed_files"
release::create_github_release "$latest_tag" "$new_tag"

main::update_develop "$develop" "$target"
git::update_develop "$develop" "$target"
}

function main::render_steps() {
local source=$1
local target=$1
local develop=$2
local target=$2
local develop=$3

echo "This script will automate the release process and follow the following steps:"
echo "- Define the branch to release: $source"
Expand All @@ -76,61 +77,3 @@ function main::render_steps() {
return
fi
}

function main::update_develop() {
local develop=$1
local target=$2

echo -e "Merging ${COLOR_ORANGE}$target${COLOR_RESET} back to" \
"${COLOR_ORANGE}$develop${COLOR_RESET} (increase the release contains hotfixes" \
"that are not in ${COLOR_ORANGE}$develop${COLOR_RESET})"

main::force_checkout "$develop"
main::merge_source_to_target "$target" "$develop"
}

function main::merge_source_to_target() {
local source=$1
local target=$2

echo -e "Merging ${COLOR_ORANGE}$source${COLOR_RESET} release to ${COLOR_ORANGE}$target${COLOR_RESET}"

if [[ "$DRY_RUN" == true ]]; then
echo -e "${COLOR_CYAN}--dry-run enabled. Skipping git merge ($source into $target)${COLOR_RESET}"
return
fi

if ! git merge "$source"; then
echo -e "${COLOR_RED}Merge failed. Please resolve conflicts and try again.${COLOR_RESET}"
exit 1
fi

git push origin "$target" --no-verify

}

function main::force_checkout() {
local branch_name="${1#origin/}" # Remove 'origin/' prefix if present

if [[ "$DRY_RUN" == true ]]; then
echo -e "${COLOR_CYAN}--dry-run enabled. Skipping git fetch & checkout${COLOR_RESET}" \
"${COLOR_ORANGE}origin/$branch_name${COLOR_RESET}"
return
fi

git config advice.detachedHead false
[ -f .git/hooks/post-checkout ] && mv .git/hooks/post-checkout .git/hooks/post-checkout.bak

git fetch origin

if git rev-parse --verify "$branch_name" >/dev/null 2>&1; then
# If branch exists locally, force checkout it
git checkout -f "$branch_name"
else
# If branch doesn't exist, create a new local branch from the remote
git checkout -b "$branch_name" origin/"$branch_name"
fi

[ -f .git/hooks/post-checkout.bak ] && mv .git/hooks/post-checkout.bak .git/hooks/post-checkout
git config advice.detachedHead true
}
58 changes: 57 additions & 1 deletion tests/e2e/main_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,61 @@ function set_up() {
function test_main_without_args() {
spy gh
spy git
assert_match_snapshot "$($SCRIPT)"
assert_match_snapshot "$($SCRIPT -h)"
}

function test_main_input_given_not_positive() {
skip "mocks are not ready yet to work on subshell..." && return
spy gh
spy read
export REPLY=n

mock io::confirm_or_exit echo confirming_io
mock git::status echo "mocked git::status"
mock git::fetch_origin echo "mocked git::fetch_origin"
mock git::changed_files echo "mocked git::changed_files"
mock git::latest_tag echo "mocked git::latest_tag"
mock git::force_checkout echo "mocked git::force_checkout"
mock git::update_develop echo "mocked git::update_develop"
mock git::merge_source_to_target echo "mocked git::merge_source_to_target"

assert_match_snapshot "$($SCRIPT --dry-run -f)"
}

function test_main_no_changed_files() {
skip "mocks are not ready yet to work on subshell..." && return

spy gh
spy read
export REPLY=y

mock io::confirm_or_exit echo confirming_io
mock git::status echo "mocked git::status"
mock git::fetch_origin echo "mocked git::fetch_origin"
mock git::changed_files echo "mocked git::changed_files"
mock git::latest_tag echo "mocked git::latest_tag"
mock git::force_checkout echo "mocked git::force_checkout"
mock git::update_develop echo "mocked git::update_develop"
mock git::merge_source_to_target echo "mocked git::merge_source_to_target"

assert_match_snapshot "$($SCRIPT --dry-run -f)"
}

# idea: override bashunit mock - to write to /tmp/mocks.sh
function mock() {
local command=$1
shift

if [[ $# -gt 0 ]]; then
eval "function $command() { $*; }"
else
eval "function $command() { echo \"${CAT:-Mocked output}\"; }"
fi

export -f "${command?}"

# shellcheck disable=SC2005
echo "$(declare -f "$command")" >> /tmp/mocks.sh
chmod +x /tmp/mocks.sh
trap 'rm -f /tmp/mocks.sh' EXIT
}