From 4f194e2141fc72023dcf0ace4651475839392f5b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20H=C3=B6ppner?= <jonas.hoeppner@garz-fricke.com>
Date: Fri, 18 Mar 2022 13:23:07 +0100
Subject: [PATCH] CI: Check: integration branch may have multiple commits

As all projects are commited in the same branch the 'up-to-date' check
may not only check if the first parent commit points to the
master/dunfell branch. Now it is needed to loop through the history
until the integration branch's commit is found.

On fail a message is displayed which merge request needs to be
retriggered manually. This can now also be the 'parent'-MR that
triggered the complete chain.

The check job is used pipeline again.

The retrigger job also looks in the .gitlab-ci project for check jobs to
retrigger.
---
 .gitlab-ci.yml                               | 35 ++++++++++--
 check_if_integration_branch_is_up_to_date.py | 56 +++++++++++++++-----
 deploy_gitlab_ci.py                          |  8 +--
 foobar-manifest-integration.yml              | 19 +++++--
 foobar-manifest.yml                          |  4 ++
 gitlab-ci-integration.jinja2                 |  4 ++
 update_submodule.py                          |  6 +--
 7 files changed, 105 insertions(+), 27 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3e01dbc1..2db6bb7b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -19,8 +19,7 @@ default:
 stages:
   - analyze
   - deploy
-  - integrate
-  - test
+  - check
 
 workflow:
   rules:
@@ -70,6 +69,7 @@ workflow:
       --manifest-project=${MANIFEST_PROJECT}
       --submodule=.gitlab-ci
       --revision=${CI_COMMIT_SHA}
+      --verbose
       ${MERGE}
       ${DEPLOY_TO}
 
@@ -78,6 +78,7 @@ workflow:
       --image=${CI_IMAGE_PYTHON}
       --branch="integrate/${CI_PROJECT_NAME}/${CI_COMMIT_REF_NAME}"
       --manifest-project=${MANIFEST_PROJECT}
+      --parent_merge_request="${CI_MERGE_REQUEST_PROJECT_URL}/-/merge_requests/${CI_MERGE_REQUEST_IID}"
       --verbose
       ${DEPLOY_TO}
       > integration.yml
@@ -99,13 +100,41 @@ deploy-foobar:
       ${PROJECT_ROOT}/minimal-bar
 
 trigger-foobar:
-  stage: integrate
+  stage: deploy
+  needs: [deploy-foobar]
   trigger:
     include:
       - artifact: integration.yml
         job: deploy-foobar
     strategy: depend
 
+# --------------------------------------------------------------------------------------
+# Stage: check
+# --------------------------------------------------------------------------------------
+check:
+  stage: check
+  rules:
+    - if: $CI_MERGE_REQUEST_IID
+  tags:
+    - infrastructure
+  timeout: 2m
+  script:
+    - cd ${CI_PROJECT_DIR}
+    - MERGE_REQUEST="${CI_MERGE_REQUEST_IID}";
+
+    - PROJECT_ROOT=${CI_PROJECT_ROOT_NAMESPACE}/yocto/infrastructure/ci-test
+    - MANIFEST_PROJECT=${PROJECT_ROOT}/minimal-manifest
+    - MASTER_BRANCH=master
+    - ./check_if_integration_branch_is_up_to_date.py
+        --gitlab-url=${CI_SERVER_URL}
+        --token=${GITBOT_TOKEN}
+        --manifest-project=${MANIFEST_PROJECT}
+        --integration-base=${MASTER_BRANCH}
+        --project=${CI_PROJECT_PATH}
+        --merge-request=${MERGE_REQUEST}
+        --verbose
+    # TODO repeat this for yocto manifest
+
 
 # ---------------------------------------------------------------------------------------
 # Stage: deploy
diff --git a/check_if_integration_branch_is_up_to_date.py b/check_if_integration_branch_is_up_to_date.py
index 9a0b0527..a6eeb31e 100755
--- a/check_if_integration_branch_is_up_to_date.py
+++ b/check_if_integration_branch_is_up_to_date.py
@@ -4,6 +4,7 @@ import common
 import argparse
 import sys
 import tempfile
+import logging
 from furl import furl
 from git import GitCommandError, Repo
 from gitlab import Gitlab, GitlabGetError
@@ -17,10 +18,6 @@ def check_if_integration_branch_is_up_to_date(
 ):
     gitlab = manifest_project.manager.gitlab
 
-    integration_branch = common.integration_branch_name(
-        project.name, merge_request.source_branch
-    )
-
     with tempfile.TemporaryDirectory() as manifest_dir:
 
         # Construct clone url containing access token
@@ -31,17 +28,24 @@ def check_if_integration_branch_is_up_to_date(
         # Checkout manifest
         try:
             manifest_repo = Repo.clone_from(
-                clone_url.url, manifest_dir, branch=integration_branch
+                clone_url.url, manifest_dir
             )
         except GitCommandError as e:
             sys.exit("ERROR: could not clone manifest repository\n" + str(e))
 
-        branch = common.find_gitlab_ci_integration_branch( 
+
+        # Handle gitlab-ci integration
+        integration_branch = common.find_gitlab_ci_integration_branch( 
                 manifest_repo, 
                 merge_request.source_branch
         )
-        if branch is not None:
-            integration_branch = branch
+        if integration_branch is None:
+            integration_branch = common.integration_branch_name(
+                project.name, merge_request.source_branch
+            )
+
+        logging.debug("Checking integration branch: %s", integration_branch)
+        manifest_repo.git.checkout('-b', integration_branch, "origin/{}".format(integration_branch))
 
         # Get branches
         try:
@@ -53,10 +57,20 @@ def check_if_integration_branch_is_up_to_date(
         except IndexError:
             sys.exit("ERROR: branch '%s' not found" % integration_base)
 
-        # The integration branch is up to date if its parent is the integration base
-        up_to_date = integration_branch.commit.parents[0] == integration_base.commit
+        logging.debug("Checking integration base: %s", integration_base)
+        parent = integration_branch.commit.parents[0]
+        while True:
+            logging.debug("Parent of integration branch: %s", parent)
+            # The integration branch is up to date if its parent is the integration base
+            if parent == integration_base.commit:
+                logging.debug("Found match")
+                return True
+            if len(parent.parents) > 0:
+                parent = parent.parents[0]
+            else:
+                return False
 
-    return up_to_date
+    return False
 
 
 def main():
@@ -97,11 +111,25 @@ def main():
         dest="merge_request",
         required=True,
     )
+    parser.add_argument(
+        "--parent-merge-request",
+        help="""parent merge requests link, only used for a hint when the check failes""",
+        dest="parent_merge_request",
+    )
+    parser.add_argument(
+        "-v",
+        "--verbose",
+        action="store_true",
+        help="""Increase verbosity.""",
+    )
 
     args, _ = parser.parse_known_args()
+    if args.verbose:
+        logging.basicConfig(level=logging.DEBUG)
 
     gitlab = Gitlab(args.gitlab_url, private_token=args.token)
 
+    logging.debug(args)
     manifest_project = common.get_project(gitlab, args.manifest_project)
     project = common.get_project(gitlab, args.project)
     merge_request = common.get_merge_request( project, args.merge_request)
@@ -119,11 +147,15 @@ def main():
     ):
         print("Integration branch is up to date.")
     else:
+        mr_url=merge_request.web_url + "/pipelines"
+        if args.parent_merge_request is not None:
+            mr_url=args.parent_merge_request + "/pipelines"
+
         sys.exit(
             "Integration branch is not up to date. Please re-run the MR pipeline:\n"
             "  1. Open the MR pipelines page:\n"
             "     %s\n"
-            "  2. Click 'Run Pipeline'" % (merge_request.web_url + "/pipelines")
+            "  2. Click 'Run Pipeline'" % (mr_url)
         )
 
 
diff --git a/deploy_gitlab_ci.py b/deploy_gitlab_ci.py
index d9eb0390..24df87b0 100755
--- a/deploy_gitlab_ci.py
+++ b/deploy_gitlab_ci.py
@@ -25,7 +25,7 @@ def update_rev_in_gitlab_ci(repo, submodule_project, submodule_revision):
         repo.git.add(gitlab_ci_yml)
 
 
-def deploy_into(project, submodule, revision, branch, replace_exising_branch=False):
+def deploy_into(project, submodule, revision, branch, replace_existing_branch=False):
     """Update the submodule and include refs to the submodule in the given project.
         Create mergerequest if needed.
 
@@ -34,7 +34,7 @@ def deploy_into(project, submodule, revision, branch, replace_exising_branch=Fal
         submodule_name (string): The name of the submodule to pull
         submodule_revision (hex string): The sha hash of the commit to update the submodule to
         branch (string): branch to update, if None, the projects default branch is used
-        replace_exising_branch: When an existing integration branch is found it is always replaced.
+        replace_existing_branch: When an existing integration branch is found it is always replaced.
 
     Returns: tuple of:
         branch (string): Name of the newly created integration branch
@@ -47,7 +47,7 @@ def deploy_into(project, submodule, revision, branch, replace_exising_branch=Fal
         revision,
         branch,
         pre_commit_hook=update_rev_in_gitlab_ci,
-        replace_exising_branch=replace_exising_branch,
+        replace_existing_branch=replace_existing_branch,
     )
 
     logging.debug("Integration branch: %s", integration_branch)
@@ -156,7 +156,7 @@ def main():
         args.submodule,
         args.revision,
         args.branch,
-        replace_exising_branch=len(args.projects) > 0,
+        replace_existing_branch=len(args.projects) > 0,
     )
     merge_request_manifest = mr
     merge_requests = []
diff --git a/foobar-manifest-integration.yml b/foobar-manifest-integration.yml
index 89f73876..f29b147b 100644
--- a/foobar-manifest-integration.yml
+++ b/foobar-manifest-integration.yml
@@ -84,9 +84,9 @@ integrate:
     - printenv
     - cd ${CI_PROJECT_DIR}
     - if [ -n "${CI_MERGE_REQUEST_IID}" ];then
-      MERGE_REQUEST="${CI_MERGE_REQUEST_IID}";
+        MERGE_REQUEST="${CI_MERGE_REQUEST_IID}";
       else
-      MERGE_REQUEST="${CI_OPEN_MERGE_REQUESTS%%,*}";
+        MERGE_REQUEST="${CI_OPEN_MERGE_REQUESTS%%,*}";
       fi
     - .gitlab-ci/integrate_into_manifest.py
         --gitlab-url=${CI_SERVER_URL}
@@ -148,18 +148,25 @@ check:
   rules:
     - if: $CI_MERGE_REQUEST_IID
     # Explicitly allow externally triggered pipelines in every case
-    # TODO disabled for now, - if: $CI_PIPELINE_SOURCE == "pipeline" || $CI_PIPELINE_SOURCE == "api"
+    - if: $CI_PIPELINE_SOURCE == "pipeline" || $CI_PIPELINE_SOURCE == "api"
   needs: ["integrate"]
   tags:
     - infrastructure
   timeout: 2m
   script:
+    - printenv
     - cd ${CI_PROJECT_DIR}
     - if [ -n "${CI_MERGE_REQUEST_IID}" ];then
-      MERGE_REQUEST="${CI_MERGE_REQUEST_IID}";
+        MERGE_REQUEST="${CI_MERGE_REQUEST_IID}";
       else
-      MERGE_REQUEST="${CI_OPEN_MERGE_REQUESTS%%,*}";
+        MERGE_REQUEST="${CI_OPEN_MERGE_REQUESTS%%,*}";
       fi
+    - if [ -n "${parent_merge_request}" ];then
+        PARENT_MR="--parent-merge-request=${parent_merge_request}";
+      fi
+    - echo "${parent_merge_request}"
+    - echo "${PARENT_MR}"
+    - echo "${MERGE_REQUEST}"
     - .gitlab-ci/check_if_integration_branch_is_up_to_date.py
         --gitlab-url=${CI_SERVER_URL}
         --token=${GITBOT_TOKEN}
@@ -167,3 +174,5 @@ check:
         --integration-base=${MASTER_BRANCH}
         --project=${CI_PROJECT_PATH}
         --merge-request=${MERGE_REQUEST}
+        --verbose
+        ${PARENT_MR}
diff --git a/foobar-manifest.yml b/foobar-manifest.yml
index e0f33aa2..2565a4d2 100644
--- a/foobar-manifest.yml
+++ b/foobar-manifest.yml
@@ -55,6 +55,10 @@ retrigger:
         --manifest=default.xml
         --remote=ci-test
       )
+    # Add the gitlab-ci project
+    - PROJECTS="$PROJECTS ${CI_PROJECT_ROOT_NAMESPACE}/yocto/infrastructure/gitlab-ci"
+    # TODO retrigger gitlab-ci integration also
+    # Retrigger also project in SRCREV
     - echo -e "Projects:\n${PROJECTS}"
     - for PROJECT in ${PROJECTS}; do
         .gitlab-ci/retrigger_mr_pipeline_jobs.py
diff --git a/gitlab-ci-integration.jinja2 b/gitlab-ci-integration.jinja2
index 14ab4729..8fc3d663 100644
--- a/gitlab-ci-integration.jinja2
+++ b/gitlab-ci-integration.jinja2
@@ -31,6 +31,10 @@ default:
 {{ projectshort }}:
   stage: integrate
   needs: [ {{ projectneeds.project }} ]
+{% if parent_merge_request is defined %}
+  variables:
+    parent_merge_request: {{ parent_merge_request }}
+{% endif %}
   trigger:
     project: {{ project }}
     branch: {{ branch }}
diff --git a/update_submodule.py b/update_submodule.py
index 3caa7dfc..9a7273e5 100755
--- a/update_submodule.py
+++ b/update_submodule.py
@@ -17,7 +17,7 @@ def update_submodule(
     submodule_revision,
     branch=None,
     pre_commit_hook=None,
-    replace_exising_branch=False,
+    replace_existing_branch=False,
 ):
     """Update submodule of gitlab project to given revision
 
@@ -28,7 +28,7 @@ def update_submodule(
         branch (string): branch to update, if None, the projects default branch is used
         pre_commit_hook: Function to be called before the actual commit is done, to add additional changes.
                          Arguments passed: ( repo, submodule_project, submodule_revision)
-        replace_exising_branch: When an existing integration branch is found it is always replaced.
+        replace_existing_branch: When an existing integration branch is found it is always replaced.
     Returns: tuple of:
         branch (string): Name of the newly created integration branch
         revision (string): hexsha of the new commit
@@ -124,7 +124,7 @@ def update_submodule(
         if existing_branch:
             repo.head.set_reference(existing_branch)
             submodule = common.get_submodule(repo, submodule_name)
-            if replace_exising_branch or submodule.hexsha != submodule_revision:
+            if replace_existing_branch or submodule.hexsha != submodule_revision:
                 print("Replacing outdated integration branch %s" % integration_branch)
                 repo.head.set_reference(branch)
                 submodule = common.get_submodule(repo, submodule_name)
-- 
GitLab