From 426bff553972881eec69b984c1c0628d1cfddd86 Mon Sep 17 00:00:00 2001
From: Nikolay Shirokovskiy <nshirokovskiy@tarantool.org>
Date: Wed, 25 Oct 2023 17:22:39 +0300
Subject: [PATCH] ci: add a workflow to check for entrypoint tags

Check check-entrypoint.sh comment for explanation of what entrypoint tag
is. The workflow fails if current branch does not have a most recent
entrypoint tag that it should have.

Part of #8319

NO_TEST=ci
NO_CHANGELOG=ci
NO_DOC=ci

(cherry picked from commit c06d0d1491572a489f07bce9a355e2a95608bc7e)
---
 .github/workflows/tree.yml    | 53 ++++++++++++++++++++++
 tools/check-entrypoint-tag.sh | 84 +++++++++++++++++++++++++++++++++++
 2 files changed, 137 insertions(+)
 create mode 100644 .github/workflows/tree.yml
 create mode 100755 tools/check-entrypoint-tag.sh

diff --git a/.github/workflows/tree.yml b/.github/workflows/tree.yml
new file mode 100644
index 0000000000..a415aa07d9
--- /dev/null
+++ b/.github/workflows/tree.yml
@@ -0,0 +1,53 @@
+name: tree
+
+on:
+  push:
+    branches:
+      - 'master'
+      - 'release/**'
+
+concurrency:
+  # Update of a developer branch cancels the previously scheduled workflow
+  # run for this branch. However, the 'master' branch, release branch, and
+  # tag workflow runs are never canceled.
+  #
+  # We use a trick here: define the concurrency group as 'workflow run ID' +
+  # 'workflow run attempt' because it is a unique combination for any run.
+  # So it effectively discards grouping.
+  #
+  # Important: we cannot use `github.sha` as a unique identifier because
+  # pushing a tag may cancel a run that works on a branch push event.
+  group: ${{ (
+    github.ref == 'refs/heads/master' ||
+    startsWith(github.ref, 'refs/heads/release/') ||
+    startsWith(github.ref, 'refs/tags/')) &&
+    format('{0}-{1}', github.run_id, github.run_attempt) ||
+    format('{0}-{1}', github.workflow, github.ref) }}
+  cancel-in-progress: true
+
+jobs:
+  entrypoint-tag:
+    if: github.repository == 'tarantool/tarantool'
+
+    runs-on: ubuntu-20.04-self-hosted
+
+    container:
+      image: docker.io/tarantool/testing:ubuntu-jammy
+
+    steps:
+      - name: Prepare checkout
+        uses: tarantool/actions/prepare-checkout@master
+
+      - name: Sources checkout
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+
+      - name: Check entrypoint tag
+        run: ./tools/check-entrypoint-tag.sh
+
+      - name: Send VK Teams message on failure
+        if: failure()
+        uses: ./.github/actions/report-job-status
+        with:
+          bot-token: ${{ secrets.VKTEAMS_BOT_TOKEN }}
diff --git a/tools/check-entrypoint-tag.sh b/tools/check-entrypoint-tag.sh
new file mode 100755
index 0000000000..66e0658a42
--- /dev/null
+++ b/tools/check-entrypoint-tag.sh
@@ -0,0 +1,84 @@
+#!/bin/bash
+#
+# This script checks that current branch has most recent entrypoint tag.
+# Entrypoint tag is annotated tag. Release tags are also expected to
+# be annotated tags.
+#
+# There are 2 cases when we require entrypoint tag.
+#
+# 1. We require that after release tag (like 2.11.3) the next commit
+# has entrypoint tag (2.11.4-entrypoint).
+#
+# 2. After branching. For example we develop 3.0.0 in master. And decide
+# to create 3.0 branch. The first commint in 3.0 branch is required
+# to have some tag (like 3.0.0-rc1, 3.0.0 or whatever). The first commit after
+# fork in master branch is required to have  entrypoint tag (3.1.0-entrypoint).
+#
+# Note that in both cases we do not check that entrypoint tag has proper
+# suffix or numbers.
+#
+# We check for most recent entrypoint tag only. For example if there
+# are tags 2.10.0 and 2.10.1 we only check for 2.10.2-entrypoint.
+#
+# Expected branches names:
+#
+# - master
+# - release/*
+
+set -eo pipefail
+
+error() {
+   echo "$@" 1>&2
+   exit 1
+}
+
+#########
+# Case 1.
+#########
+
+# Match digit only release tags like 2.10.0.
+pattern='^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$'
+# Get most recent tag in the HEAD ancestry.
+tag=`git describe --abbrev=0`
+# If it is a release tag.
+if [[ "$tag" =~ $pattern ]]; then
+    # Find the commit just after the release tag in the HEAD ancestry.
+    # It is not tagged as entrypoint because it was not seen by the
+    # describe command above.
+    entrypoint=`git rev-list HEAD ^$tag | tail -n1`
+    if [[ $entrypoint ]]; then
+        error "Missing entrypoint tag for commit $entrypoint after release"\
+              "tag $tag."
+    fi
+fi
+
+#########
+# Case 2.
+#########
+
+# Find current branch (report HEAD for 'detached HEAD' state).
+branch=`git rev-parse --abbrev-ref HEAD`
+if [[ "$branch" =~ ^(master|release/.*)$ ]]; then
+    # We need to find the commit that starts this branch (i.e. that the first
+    # commit on this branch after the commit that is common for two branches.)
+    #
+    # In order to achieve this we find all the commits of this branch that
+    # are not on other branches from release/* && master set.
+    #
+    # Unfortunately I did not find a way to set arguments for git rev-list
+    # without this branch.
+    if [[ "$branch" = master ]]; then
+        not_remotes="--remotes=origin/release/*"
+    else
+        not_remotes="--exclude origin/$branch --remotes=origin/release/* origin/master"
+    fi
+    entrypoint=`git rev-list HEAD --not $not_remotes | tail -n1`
+    if [[ $entrypoint ]]; then
+        # Check if entrypoint has annotated tag.
+        git describe --exact-match $entrypoint &>/dev/null || \
+        error "Missing tag for commit $entrypoint after branching in"\
+              "branch $branch."
+    fi
+fi
+
+echo OK
-- 
GitLab