diff --git a/README.md b/README.md index 78af2b6..245d130 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,17 @@ Tracks the commits in a [git](http://git-scm.com/) repository. * `ignore_branches`: *Optional.* Used in conjunction with and applied after the `branches` pattern. See example for common use case. +* `sort`: *Optional.* When set to `semver`, branches matched by the + `branches` pattern will be sorted by semantic version (highest first) + before processing. This ensures the branch with the highest version + number is always checked first. Only meaningful in multi-branch mode. + +* `sort_prefix`: *Optional.* Used in conjunction with `sort: semver`. A + regex pattern that is stripped from the branch name before extracting + the version for comparison. For example, `(release|hotfix)/` would + strip the prefix from `release/2.0.0` and `hotfix/1.0.1` to compare + `2.0.0` vs `1.0.1`. + * `redis`: *Optional.* Contains the information required to use a specified Redis server to store multibranch historic references so that they don't clutter up the ref lines. It consists of the following subkeys: @@ -102,6 +113,19 @@ resources: ignore_branches: '(master|deploy)' ``` +Detecting the highest version release or hotfix branch first + +``` yaml +resources: +- name: my-releases + type: git-multibranch + source: + uri: git@github.com:my-org/my-repo.git + branches: '(release|hotfix)/\d+\.\d+\.\d+' + sort: semver + sort_prefix: '(release|hotfix)/' +``` + ## Behavior ### `check`: Check for new commits. diff --git a/assets/check b/assets/check index 08d2c92..48b7ad8 100755 --- a/assets/check +++ b/assets/check @@ -26,6 +26,8 @@ ignore_branches=$(jq -r '.source.ignore_branches // ""' < $payload) paths="$(jq -r '(.source.paths // ["."])[]' < $payload)" # those "'s are important ignore_paths="$(jq -r '":!" + (.source.ignore_paths // [])[]' < $payload)" # these ones too last_refs=$(jq -r '.version.ref // ""' < $payload) +sort_mode=$(jq -r '.source.sort // ""' < $payload) +sort_prefix=$(jq -r '.source.sort_prefix // ""' < $payload) ## Redis stuff to support cleaner multibranch behaviour redis_host=$(jq -r '.source.redis.host // ""' < $payload) @@ -75,6 +77,29 @@ filter_branches() { echo "$branch_refs" } +sort_branches_semver() { + # When sort mode is semver, strip the prefix from branch names, + # sort by major.minor.patch descending, then restore original format. + # This ensures the highest version is processed first. + if [ "$sort_mode" = "semver" ] ; then + local input="$(cat)" + if [ -n "$sort_prefix" ] ; then + echo "$input" | \ + awk -F: -v prefix="$sort_prefix" '{ + branch = $2; + ver = branch; + gsub(prefix, "", ver); + print ver "\t" $0 + }' | sort -t. -k1,1rn -k2,2rn -k3,3rn | \ + cut -f2 + else + echo "$input" + fi + else + cat + fi +} + get_refs() { local ref="$1" git log --grep '\[ci skip\]' --invert-grep --format='%H' $(log_range $ref) $(paths_search "$paths" "$ignore_paths") @@ -128,7 +153,8 @@ get_branch_refs() { grep -e '^[0-9a-f]\{40\}:' | \ sort -t: -k1 | grep ":origin/" | \ sed -e 's/:origin\//:/' | \ - filter_branches)" + filter_branches | \ + sort_branches_semver)" # Find the first branch that has a different ref for branch_and_ref in $current_branches ; do diff --git a/test/check_multi.sh b/test/check_multi.sh index 8540145..d4addd7 100755 --- a/test/check_multi.sh +++ b/test/check_multi.sh @@ -296,6 +296,50 @@ it_can_find_successive_branches_with_multiple_commits_with_redis() { fi } +it_sorts_branches_by_semver_highest_first() { + local repo=$(init_repo) + + # Create release branches with different versions + local ref1=$(make_commit_to_branch $repo "release/1.0.0") + local ref2=$(make_commit_to_branch $repo "release/2.0.0") + local ref3=$(make_commit_to_branch $repo "release/1.5.0") + + # With semver sort, the highest version (release/2.0.0) should always be returned first + # regardless of commit hash ordering + local result=$(test_check uri $repo branches 'release/.*' sort semver sort_prefix 'release/') + + local returned_branch=$(echo "$result" | jq -r '.[0].ref' | sed 's/^[0-9a-f]*://' | awk '{print $1}') + + if [ "$returned_branch" != "release/2.0.0" ] ; then + echo "Expected release/2.0.0 to be returned first, got: $returned_branch" + return 1 + fi + + echo "Correctly returned release/2.0.0 (highest semver) first" +} + +it_sorts_mixed_release_hotfix_branches_by_semver() { + local repo=$(init_repo) + + # Create mixed release and hotfix branches + local ref1=$(make_commit_to_branch $repo "release/1.0.0") + local ref2=$(make_commit_to_branch $repo "hotfix/1.0.1") + local ref3=$(make_commit_to_branch $repo "release/2.0.0") + local ref4=$(make_commit_to_branch $repo "hotfix/2.0.1") + + # With semver sort and prefix stripping, hotfix/2.0.1 (version 2.0.1) should be first + local result=$(test_check uri $repo branches '(release|hotfix)/.*' sort semver sort_prefix '(release|hotfix)/') + + local returned_branch=$(echo "$result" | jq -r '.[0].ref' | sed 's/^[0-9a-f]*://' | awk '{print $1}') + + if [ "$returned_branch" != "hotfix/2.0.1" ] ; then + echo "Expected hotfix/2.0.1 to be returned first (highest semver 2.0.1), got: $returned_branch" + return 1 + fi + + echo "Correctly returned hotfix/2.0.1 (highest semver 2.0.1) first" +} + # --- RUN TESTS --- run it_can_perform_initial_check @@ -308,3 +352,5 @@ run it_can_find_successive_branches_with_multiple_commits run it_ignores_branches_with_only_skip_commits run it_can_find_branches_that_has_multiple_commits_with_latest_being_skipped run it_can_find_successive_branches_with_multiple_commits_with_redis +run it_sorts_branches_by_semver_highest_first +run it_sorts_mixed_release_hotfix_branches_by_semver diff --git a/test/helpers.sh b/test/helpers.sh index 723eb79..94b216f 100644 --- a/test/helpers.sh +++ b/test/helpers.sh @@ -192,6 +192,22 @@ test_check() { }")" shift;; + "sort" ) + addition="$(jq -n "{ + source: { + sort: $(echo "$1" | jq -R '.') + } + }")" + shift;; + + "sort_prefix" ) + addition="$(jq -n "{ + source: { + sort_prefix: $(echo "$1" | jq -R '.') + } + }")" + shift;; + "redis" ) addition="$(jq -n "{ source: {