From ae32f3432d4d963f4b1c1db56b3a16b10b34e9df Mon Sep 17 00:00:00 2001 From: Leonid Ivashkevich Date: Wed, 29 Oct 2025 10:22:18 +0300 Subject: [PATCH 1/7] Add Protected Branch Merge Strategy --- src/main/java/service/GitLabService.java | 76 ++++++++++++++++++++--- src/main/resources/application.properties | 3 +- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/main/java/service/GitLabService.java b/src/main/java/service/GitLabService.java index 5666318..fa403d4 100644 --- a/src/main/java/service/GitLabService.java +++ b/src/main/java/service/GitLabService.java @@ -28,6 +28,7 @@ import org.gitlab4j.api.models.Branch; import org.gitlab4j.api.models.MergeRequest; import org.gitlab4j.api.models.MergeRequestParams; +import org.gitlab4j.api.models.ProtectedBranch; import org.gitlab4j.models.Constants.MergeRequestState; import controller.model.CascadeResult; @@ -60,12 +61,16 @@ public class GitLabService { @ConfigProperty(name = "gitlab.api.token.approver") Optional apiTokenApprover; + @ConfigProperty(name = "protected.branch.merge.strategy") + boolean protectedBranchMergeStrategy; + @Inject GitInfo gitInfo; @Inject BuildInfo buildInfo; + private static final String BRANCH_SEARCH_QUERY_PARAM_TEMPLATE = "^%s$"; private static final String UCASCADE_CONFIGURATION_FILE = "ucascade.json"; private static final String UCASCADE_TAG = "[ucascade]"; private static final String UCASCADE_BRANCH_PATTERN_PREFIX = "^mr(\\d+)_"; @@ -207,14 +212,9 @@ private void mergePreviousAutoMr(CascadeResult result, String gitlabEventUUID, L } private void createAutoMr(CascadeResult result, String gitlabEventUUID, Long projectId, String prevMrSourceBranch, String sourceBranch, Long mrNumber, String mergeSha) { - String branchModel = getBranchModelConfigurationFile(gitlabEventUUID, projectId, mergeSha); - String nextMainBranch = ConfigurationUtils.getNextTargetBranch(branchModel, sourceBranch); - - if (nextMainBranch != null) { - Branch branch = getBranch(gitlabEventUUID, projectId, nextMainBranch); - if (!Branch.isValid(branch)) { - throw new IllegalStateException(String.format("GitlabEvent: '%s' | Branch named '%s' does not exist in project '%d'. Please check the ucascade configuration file.", gitlabEventUUID, nextMainBranch, projectId)); - } + Branch branch = getNextTargetBranch(gitlabEventUUID, projectId, sourceBranch, mergeSha); + if (branch != null) { + String nextMainBranch = branch.getName(); if (haveDiff(gitlabEventUUID, projectId, mergeSha, nextMainBranch)) { String tmpBranchName = "mr" + mrNumber + "_" + sourceBranch; createBranch(gitlabEventUUID, projectId, tmpBranchName, mergeSha); @@ -570,6 +570,66 @@ private String getBranchModelConfigurationFile(String gitlabEventUUID, Long proj } } + private Branch getNextTargetBranch(String gitlabEventUUID, Long projectId, String sourceBranch, String mergeSha) { + if (protectedBranchMergeStrategy) { + return getNextTargetBranchFromProtectedBranches(gitlabEventUUID, projectId, sourceBranch, mergeSha); + } else { + return getNextTargetBranchFromBranchModel(gitlabEventUUID, projectId, sourceBranch, mergeSha); + } + } + + private Branch getNextTargetBranchFromBranchModel(String gitlabEventUUID, Long projectId, String sourceBranch, String mergeSha) { + String branchModel = getBranchModelConfigurationFile(gitlabEventUUID, projectId, mergeSha); + String nextMainBranch = ConfigurationUtils.getNextTargetBranch(branchModel, sourceBranch); + Branch targetBranch = null; + if (nextMainBranch != null) { + targetBranch = getBranch(gitlabEventUUID, projectId, nextMainBranch); + if (!Branch.isValid(targetBranch)) { + throw new IllegalStateException(String.format("GitlabEvent: '%s' | Branch named '%s' does not exist in projectId '%d'. Please check the ucascade configuration file.", gitlabEventUUID, nextMainBranch, projectId)); + } + } + return targetBranch; + } + + private Branch getNextTargetBranchFromProtectedBranches(String gitlabEventUUID, Long projectId, String sourceBranch, String mergeSha) { + try { + List protectedBranches = gitlab.getProtectedBranchesApi().getProtectedBranches(projectId); + boolean sourceBranchFounded = false; + Branch targetBranch = null; + boolean withWildcardRules = protectedBranches.stream().anyMatch(pb -> pb.getName().contains("*")); + if (withWildcardRules) { + outerLoop: for (ProtectedBranch protectedBranch : protectedBranches) { + String search = BRANCH_SEARCH_QUERY_PARAM_TEMPLATE.formatted(protectedBranch.getName()); + List branches = gitlab.getRepositoryApi().getBranches(projectId, search); + for (Branch branch : branches) { + if (sourceBranchFounded) { + targetBranch = branch; + break outerLoop; + } + if (branch.getName().equals(sourceBranch)) { + sourceBranchFounded = true; + } + } + sourceBranchFounded = false; + } + } else { + for (ProtectedBranch protectedBranch : protectedBranches) { + Branch branch = gitlab.getRepositoryApi().getBranch(projectId, protectedBranch.getName()); + if (sourceBranchFounded) { + targetBranch = branch; + break; + } + if (branch.getName().equals(sourceBranch)) { + sourceBranchFounded = true; + } + } + } + return targetBranch; + } catch (GitLabApiException e) { + throw new IllegalStateException(String.format("GitlabEvent: '%s' | Cannot retrieve protected branch '%s'", gitlabEventUUID, sourceBranch), e); + } + } + private boolean hasPipeline(String gitlabEventUUID, MergeRequest mr) { try { MergeRequestApi mrApi = gitlab.getMergeRequestApi(); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 059e458..987015b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,4 +1,5 @@ -gitlab.host=https://gitlab.com +gitlab.host=https://vyruchai.gitlab.yandexcloud.net +protected.branch.merge.strategy=true quarkus.container-image.tag=latest quarkus.info.git.enabled=true From f01b32fd7884096e891a26559a544c9b4eb08264 Mon Sep 17 00:00:00 2001 From: Leonid Ivashkevich Date: Wed, 29 Oct 2025 10:43:19 +0300 Subject: [PATCH 2/7] Add Protected Branch Merge Strategy --- src/main/resources/application.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 987015b..133ca41 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,5 @@ -gitlab.host=https://vyruchai.gitlab.yandexcloud.net -protected.branch.merge.strategy=true +gitlab.host=https://gitlab.com +protected.branch.merge.strategy=false quarkus.container-image.tag=latest quarkus.info.git.enabled=true From 4f39f119b633f8cd5c3e16544f14dafea1cd9532 Mon Sep 17 00:00:00 2001 From: Leonid Ivashkevich Date: Wed, 29 Oct 2025 10:48:25 +0300 Subject: [PATCH 3/7] Add Protected Branch Merge Strategy --- src/main/java/service/GitLabService.java | 2 +- src/main/resources/application.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/service/GitLabService.java b/src/main/java/service/GitLabService.java index fa403d4..6be3987 100644 --- a/src/main/java/service/GitLabService.java +++ b/src/main/java/service/GitLabService.java @@ -61,7 +61,7 @@ public class GitLabService { @ConfigProperty(name = "gitlab.api.token.approver") Optional apiTokenApprover; - @ConfigProperty(name = "protected.branch.merge.strategy") + @ConfigProperty(name = "protected.branch.merge.strategy", defaultValue = "false") boolean protectedBranchMergeStrategy; @Inject diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 133ca41..d1f7ef2 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,5 @@ gitlab.host=https://gitlab.com -protected.branch.merge.strategy=false + quarkus.container-image.tag=latest quarkus.info.git.enabled=true From 366d905ad8ef068b95367bfbcfd5df03612c4ef6 Mon Sep 17 00:00:00 2001 From: Leonid Ivashkevich Date: Wed, 29 Oct 2025 10:52:00 +0300 Subject: [PATCH 4/7] Add Protected Branch Merge Strategy --- src/main/resources/application.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d1f7ef2..059e458 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,4 @@ gitlab.host=https://gitlab.com - quarkus.container-image.tag=latest quarkus.info.git.enabled=true From 94c9e6ca937c0045fe393981078b43d9eb469e54 Mon Sep 17 00:00:00 2001 From: Leonid Ivashkevich Date: Wed, 29 Oct 2025 17:43:14 +0300 Subject: [PATCH 5/7] Add Protected Branch Alphanumeric Sort --- build.gradle | 1 + src/main/java/enums/MergeStrategy.java | 7 ++++ src/main/java/service/GitLabService.java | 53 ++++++++++++++---------- 3 files changed, 39 insertions(+), 22 deletions(-) create mode 100644 src/main/java/enums/MergeStrategy.java diff --git a/build.gradle b/build.gradle index 5f69d5b..13c76fa 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,7 @@ dependencies { implementation 'io.quarkus:quarkus-arc' implementation 'org.gitlab4j:gitlab4j-api:6.0.0-rc.8' implementation 'eu.mulk.quarkus-googlecloud-jsonlogging:quarkus-googlecloud-jsonlogging:6.0.0' + implementation 'se.sawano.java:alphanumeric-comparator:2.0.0' testImplementation 'io.quarkus:quarkus-junit5' testImplementation 'io.rest-assured:rest-assured' testImplementation 'com.github.tomakehurst:wiremock-jre8:2.33.2' diff --git a/src/main/java/enums/MergeStrategy.java b/src/main/java/enums/MergeStrategy.java new file mode 100644 index 0000000..979f0aa --- /dev/null +++ b/src/main/java/enums/MergeStrategy.java @@ -0,0 +1,7 @@ +package enums; + +public enum MergeStrategy { + CONFIGURATION_FILE, + PROTECTED_BRANCHES + +} diff --git a/src/main/java/service/GitLabService.java b/src/main/java/service/GitLabService.java index 6be3987..4bda7e0 100644 --- a/src/main/java/service/GitLabService.java +++ b/src/main/java/service/GitLabService.java @@ -36,12 +36,14 @@ import controller.model.MergeRequestResult; import controller.model.MergeRequestSimple; import controller.model.MergeRequestUcascadeState; +import enums.MergeStrategy; import io.quarkus.info.BuildInfo; import io.quarkus.info.GitInfo; import io.quarkus.logging.Log; import io.quarkus.vertx.ConsumeEvent; import io.smallrye.common.annotation.Blocking; import io.vertx.mutiny.core.eventbus.EventBus; +import se.sawano.java.text.AlphanumericComparator; import util.ConfigurationUtils; @ApplicationScoped @@ -61,8 +63,8 @@ public class GitLabService { @ConfigProperty(name = "gitlab.api.token.approver") Optional apiTokenApprover; - @ConfigProperty(name = "protected.branch.merge.strategy", defaultValue = "false") - boolean protectedBranchMergeStrategy; + @ConfigProperty(name = "merge.strategy", defaultValue = "configuration_file") + MergeStrategy mergeStrategy; @Inject GitInfo gitInfo; @@ -212,9 +214,8 @@ private void mergePreviousAutoMr(CascadeResult result, String gitlabEventUUID, L } private void createAutoMr(CascadeResult result, String gitlabEventUUID, Long projectId, String prevMrSourceBranch, String sourceBranch, Long mrNumber, String mergeSha) { - Branch branch = getNextTargetBranch(gitlabEventUUID, projectId, sourceBranch, mergeSha); - if (branch != null) { - String nextMainBranch = branch.getName(); + String nextMainBranch = getNextTargetBranch(gitlabEventUUID, projectId, sourceBranch, mergeSha); + if (nextMainBranch != null) { if (haveDiff(gitlabEventUUID, projectId, mergeSha, nextMainBranch)) { String tmpBranchName = "mr" + mrNumber + "_" + sourceBranch; createBranch(gitlabEventUUID, projectId, tmpBranchName, mergeSha); @@ -570,53 +571,61 @@ private String getBranchModelConfigurationFile(String gitlabEventUUID, Long proj } } - private Branch getNextTargetBranch(String gitlabEventUUID, Long projectId, String sourceBranch, String mergeSha) { - if (protectedBranchMergeStrategy) { - return getNextTargetBranchFromProtectedBranches(gitlabEventUUID, projectId, sourceBranch, mergeSha); - } else { - return getNextTargetBranchFromBranchModel(gitlabEventUUID, projectId, sourceBranch, mergeSha); - } + private String getNextTargetBranch(String gitlabEventUUID, Long projectId, String sourceBranch, String mergeSha) { + return switch (mergeStrategy) { + case CONFIGURATION_FILE -> getNextTargetBranchFromBranchModel(gitlabEventUUID, projectId, sourceBranch, mergeSha); + case PROTECTED_BRANCHES -> getNextTargetBranchFromProtectedBranches(gitlabEventUUID, projectId, sourceBranch, mergeSha); + }; } - private Branch getNextTargetBranchFromBranchModel(String gitlabEventUUID, Long projectId, String sourceBranch, String mergeSha) { + private String getNextTargetBranchFromBranchModel(String gitlabEventUUID, Long projectId, String sourceBranch, String mergeSha) { String branchModel = getBranchModelConfigurationFile(gitlabEventUUID, projectId, mergeSha); String nextMainBranch = ConfigurationUtils.getNextTargetBranch(branchModel, sourceBranch); - Branch targetBranch = null; + String targetBranch = null; if (nextMainBranch != null) { - targetBranch = getBranch(gitlabEventUUID, projectId, nextMainBranch); - if (!Branch.isValid(targetBranch)) { + Branch branch = getBranch(gitlabEventUUID, projectId, nextMainBranch); + if (!Branch.isValid(branch)) { throw new IllegalStateException(String.format("GitlabEvent: '%s' | Branch named '%s' does not exist in projectId '%d'. Please check the ucascade configuration file.", gitlabEventUUID, nextMainBranch, projectId)); } + targetBranch = branch.getName(); } return targetBranch; } - private Branch getNextTargetBranchFromProtectedBranches(String gitlabEventUUID, Long projectId, String sourceBranch, String mergeSha) { + private String getNextTargetBranchFromProtectedBranches(String gitlabEventUUID, Long projectId, String sourceBranch, String mergeSha) { try { List protectedBranches = gitlab.getProtectedBranchesApi().getProtectedBranches(projectId); + List sortedProtectedBranchNames = protectedBranches.stream() + .map(ProtectedBranch::getName) + .sorted(new AlphanumericComparator()) + .toList(); boolean sourceBranchFounded = false; - Branch targetBranch = null; + String targetBranch = null; boolean withWildcardRules = protectedBranches.stream().anyMatch(pb -> pb.getName().contains("*")); if (withWildcardRules) { outerLoop: for (ProtectedBranch protectedBranch : protectedBranches) { String search = BRANCH_SEARCH_QUERY_PARAM_TEMPLATE.formatted(protectedBranch.getName()); List branches = gitlab.getRepositoryApi().getBranches(projectId, search); - for (Branch branch : branches) { + List sortedBranchNames = branches.stream() + .map(Branch::getName) + .sorted(new AlphanumericComparator()) + .toList(); + for (String branch : sortedBranchNames) { if (sourceBranchFounded) { targetBranch = branch; break outerLoop; } - if (branch.getName().equals(sourceBranch)) { + if (branch.equals(sourceBranch)) { sourceBranchFounded = true; } } sourceBranchFounded = false; } } else { - for (ProtectedBranch protectedBranch : protectedBranches) { - Branch branch = gitlab.getRepositoryApi().getBranch(projectId, protectedBranch.getName()); + for (String protectedBranch : sortedProtectedBranchNames) { + Branch branch = gitlab.getRepositoryApi().getBranch(projectId, protectedBranch); if (sourceBranchFounded) { - targetBranch = branch; + targetBranch = branch.getName(); break; } if (branch.getName().equals(sourceBranch)) { From 7d0a9b07e461e2145192c6f4befebadf8da00587 Mon Sep 17 00:00:00 2001 From: Leonid Ivashkevich Date: Fri, 31 Oct 2025 12:00:49 +0300 Subject: [PATCH 6/7] cascade merge only valid protected branches --- src/main/java/service/GitLabService.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/service/GitLabService.java b/src/main/java/service/GitLabService.java index 4bda7e0..0cbe5a1 100644 --- a/src/main/java/service/GitLabService.java +++ b/src/main/java/service/GitLabService.java @@ -607,6 +607,7 @@ private String getNextTargetBranchFromProtectedBranches(String gitlabEventUUID, String search = BRANCH_SEARCH_QUERY_PARAM_TEMPLATE.formatted(protectedBranch.getName()); List branches = gitlab.getRepositoryApi().getBranches(projectId, search); List sortedBranchNames = branches.stream() + .filter(Branch::isValid) .map(Branch::getName) .sorted(new AlphanumericComparator()) .toList(); @@ -624,12 +625,14 @@ private String getNextTargetBranchFromProtectedBranches(String gitlabEventUUID, } else { for (String protectedBranch : sortedProtectedBranchNames) { Branch branch = gitlab.getRepositoryApi().getBranch(projectId, protectedBranch); - if (sourceBranchFounded) { - targetBranch = branch.getName(); - break; - } - if (branch.getName().equals(sourceBranch)) { - sourceBranchFounded = true; + if (Branch.isValid(branch)) { + if (sourceBranchFounded) { + targetBranch = branch.getName(); + break; + } + if (branch.getName().equals(sourceBranch)) { + sourceBranchFounded = true; + } } } } From 82888d9a03b1622893f0e7c9513c957e3ac4a084 Mon Sep 17 00:00:00 2001 From: Leonid Ivashkevich Date: Sat, 1 Nov 2025 09:32:31 +0300 Subject: [PATCH 7/7] Renamed The Method --- src/main/java/service/GitLabService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/service/GitLabService.java b/src/main/java/service/GitLabService.java index 0cbe5a1..44591c7 100644 --- a/src/main/java/service/GitLabService.java +++ b/src/main/java/service/GitLabService.java @@ -573,12 +573,12 @@ private String getBranchModelConfigurationFile(String gitlabEventUUID, Long proj private String getNextTargetBranch(String gitlabEventUUID, Long projectId, String sourceBranch, String mergeSha) { return switch (mergeStrategy) { - case CONFIGURATION_FILE -> getNextTargetBranchFromBranchModel(gitlabEventUUID, projectId, sourceBranch, mergeSha); + case CONFIGURATION_FILE -> getNextTargetBranchFromConfigurationFile(gitlabEventUUID, projectId, sourceBranch, mergeSha); case PROTECTED_BRANCHES -> getNextTargetBranchFromProtectedBranches(gitlabEventUUID, projectId, sourceBranch, mergeSha); }; } - private String getNextTargetBranchFromBranchModel(String gitlabEventUUID, Long projectId, String sourceBranch, String mergeSha) { + private String getNextTargetBranchFromConfigurationFile(String gitlabEventUUID, Long projectId, String sourceBranch, String mergeSha) { String branchModel = getBranchModelConfigurationFile(gitlabEventUUID, projectId, mergeSha); String nextMainBranch = ConfigurationUtils.getNextTargetBranch(branchModel, sourceBranch); String targetBranch = null;