diff --git a/src/main/java/com/crowdin/cli/client/CrowdinClientCore.java b/src/main/java/com/crowdin/cli/client/CrowdinClientCore.java index 9813d1d4..6a8d8883 100644 --- a/src/main/java/com/crowdin/cli/client/CrowdinClientCore.java +++ b/src/main/java/com/crowdin/cli/client/CrowdinClientCore.java @@ -27,29 +27,29 @@ abstract class CrowdinClientCore { private static final long defaultMillisToRetry = 100; private static final Map, RuntimeException> standardErrorHandlers = - new LinkedHashMap, RuntimeException>() {{ - put((code, message) -> code.equals("401"), - new ExitCodeExceptionMapper.AuthorizationException(RESOURCE_BUNDLE.getString("error.response.401"))); - put((code, message) -> code.equals("403") && message.contains("upgrade your subscription plan to upload more file formats"), - new ExitCodeExceptionMapper.ForbiddenException(RESOURCE_BUNDLE.getString("error.response.403_upgrade_subscription"))); - put((code, message) -> code.equals("403"), - new ExitCodeExceptionMapper.ForbiddenException(RESOURCE_BUNDLE.getString("error.response.403"))); - put((code, message) -> code.equals("404") && StringUtils.containsIgnoreCase(message, "Project Not Found"), - new ExitCodeExceptionMapper.NotFoundException(RESOURCE_BUNDLE.getString("error.response.404_project_not_found"))); - put((code, message) -> code.equals("404") && StringUtils.containsIgnoreCase(message, "Organization Not Found"), - new ExitCodeExceptionMapper.NotFoundException(RESOURCE_BUNDLE.getString("error.response.404_organization_not_found"))); - put((code, message) -> code.equals("429"), - new ExitCodeExceptionMapper.RateLimitException(RESOURCE_BUNDLE.getString("error.response.429"))); - put((code, message) -> StringUtils.containsAny(message, - "PKIX path building failed", - "sun.security.provider.certpath.SunCertPathBuilderException", - "unable to find valid certification path to requested target"), - new RuntimeException(RESOURCE_BUNDLE.getString("error.response.certificate"))); - put((code, message) -> message.equals("Name or service not known"), - new RuntimeException(RESOURCE_BUNDLE.getString("error.response.url_not_known"))); - put((code, message) -> code.equals("") && message.equals(""), - new RuntimeException("Empty error message from server")); - }}; + new LinkedHashMap<>() {{ + put((code, message) -> code.equals("401"), + new ExitCodeExceptionMapper.AuthorizationException(RESOURCE_BUNDLE.getString("error.response.401"))); + put((code, message) -> code.equals("403") && message.contains("upgrade your subscription plan to upload more file formats"), + new ExitCodeExceptionMapper.ForbiddenException(RESOURCE_BUNDLE.getString("error.response.403_upgrade_subscription"))); + put((code, message) -> code.equals("403") && !message.contains("Merge is possible only into main branch"), + new ExitCodeExceptionMapper.ForbiddenException(RESOURCE_BUNDLE.getString("error.response.403"))); + put((code, message) -> code.equals("404") && StringUtils.containsIgnoreCase(message, "Project Not Found"), + new ExitCodeExceptionMapper.NotFoundException(RESOURCE_BUNDLE.getString("error.response.404_project_not_found"))); + put((code, message) -> code.equals("404") && StringUtils.containsIgnoreCase(message, "Organization Not Found"), + new ExitCodeExceptionMapper.NotFoundException(RESOURCE_BUNDLE.getString("error.response.404_organization_not_found"))); + put((code, message) -> code.equals("429"), + new ExitCodeExceptionMapper.RateLimitException(RESOURCE_BUNDLE.getString("error.response.429"))); + put((code, message) -> StringUtils.containsAny(message, + "PKIX path building failed", + "sun.security.provider.certpath.SunCertPathBuilderException", + "unable to find valid certification path to requested target"), + new RuntimeException(RESOURCE_BUNDLE.getString("error.response.certificate"))); + put((code, message) -> message.equals("Name or service not known"), + new RuntimeException(RESOURCE_BUNDLE.getString("error.response.url_not_known"))); + put((code, message) -> code.equals("") && message.equals(""), + new RuntimeException("Empty error message from server")); + }}; /** * Util logic for downloading full lists. @@ -122,7 +122,7 @@ protected static T executeRequest(Map void searchErrorHandler(Map, R> errorHandlers, String code, String message) throws R { + private static void searchErrorHandler(Map, R> errorHandlers, String code, String message) throws R { for (Map.Entry, R> errorHandler : errorHandlers.entrySet()) { if (errorHandler.getKey().test(code, message)) { throw errorHandler.getValue(); diff --git a/src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java b/src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java index 534783e3..3ecd0f99 100644 --- a/src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java +++ b/src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java @@ -1,8 +1,6 @@ package com.crowdin.cli.client; -import com.crowdin.client.branches.model.BranchCloneStatus; -import com.crowdin.client.branches.model.CloneBranchRequest; -import com.crowdin.client.branches.model.ClonedBranch; +import com.crowdin.client.branches.model.*; import com.crowdin.client.core.model.PatchRequest; import com.crowdin.client.labels.model.AddLabelRequest; import com.crowdin.client.labels.model.Label; @@ -150,7 +148,7 @@ public Branch addBranch(AddBranchRequest request) { @Override public BranchCloneStatus cloneBranch(Long branchId, CloneBranchRequest request) throws ResponseException { - Map, ResponseException> errorHandlers = new LinkedHashMap, ResponseException>() {{ + Map, ResponseException> errorHandlers = new LinkedHashMap<>() {{ put((code, message) -> StringUtils.containsAny(message, "Name must be unique"), new ExistsResponseException()); }}; return executeRequest(errorHandlers, @@ -173,6 +171,27 @@ public ClonedBranch getClonedBranch(Long branchId, String cloneId) { .getData()); } + @Override + public BranchMergeStatus mergeBranch(Long branchId, MergeBranchRequest request) { + return executeRequest(() -> this.client.getBranchesApi() + .mergeBranch(projectId, branchId, request) + .getData()); + } + + @Override + public BranchMergeStatus checkMergeBranchStatus(Long branchId, String mergeId) { + return executeRequest(() -> this.client.getBranchesApi() + .checkMergeBranchStatus(projectId, branchId, mergeId) + .getData()); + } + + @Override + public BranchMergeSummary getBranchMergeSummary(Long branchId, String mergeId) { + return executeRequest(() -> this.client.getBranchesApi() + .getMergeBranchSummary(this.projectId, branchId, mergeId) + .getData()); + } + @Override public void deleteBranch(Long branchId) { executeRequest(() -> { diff --git a/src/main/java/com/crowdin/cli/client/ProjectClient.java b/src/main/java/com/crowdin/cli/client/ProjectClient.java index 8ec48a8e..e1844121 100644 --- a/src/main/java/com/crowdin/cli/client/ProjectClient.java +++ b/src/main/java/com/crowdin/cli/client/ProjectClient.java @@ -1,8 +1,6 @@ package com.crowdin.cli.client; -import com.crowdin.client.branches.model.BranchCloneStatus; -import com.crowdin.client.branches.model.CloneBranchRequest; -import com.crowdin.client.branches.model.ClonedBranch; +import com.crowdin.client.branches.model.*; import com.crowdin.client.core.model.PatchRequest; import com.crowdin.client.labels.model.AddLabelRequest; import com.crowdin.client.labels.model.Label; @@ -41,6 +39,12 @@ default CrowdinProjectFull downloadFullProject() { ClonedBranch getClonedBranch(Long branchId, String cloneId); + BranchMergeStatus mergeBranch(Long branchId, MergeBranchRequest request) throws ResponseException; + + BranchMergeStatus checkMergeBranchStatus(Long branchId, String mergeId); + + BranchMergeSummary getBranchMergeSummary(Long branchId, String mergeId); + void deleteBranch(Long branchId); List listBranches(); diff --git a/src/main/java/com/crowdin/cli/commands/Actions.java b/src/main/java/com/crowdin/cli/commands/Actions.java index 2596f92c..8bf32e3c 100644 --- a/src/main/java/com/crowdin/cli/commands/Actions.java +++ b/src/main/java/com/crowdin/cli/commands/Actions.java @@ -116,6 +116,8 @@ NewAction preTranslate( NewAction branchClone(String source, String target, boolean noProgress, boolean plainView); + NewAction branchMerge(String source, String target, boolean dryrun, boolean deleteAfterMerge, boolean noProgress, boolean plainView); + NewAction branchDelete(String name); NewAction screenshotList(Long stringId, boolean plainView); diff --git a/src/main/java/com/crowdin/cli/commands/actions/BranchMergeAction.java b/src/main/java/com/crowdin/cli/commands/actions/BranchMergeAction.java new file mode 100644 index 00000000..e140a932 --- /dev/null +++ b/src/main/java/com/crowdin/cli/commands/actions/BranchMergeAction.java @@ -0,0 +1,98 @@ +package com.crowdin.cli.commands.actions; + +import com.crowdin.cli.client.CrowdinProjectFull; +import com.crowdin.cli.client.ProjectClient; +import com.crowdin.cli.commands.NewAction; +import com.crowdin.cli.commands.Outputter; +import com.crowdin.cli.commands.picocli.ExitCodeExceptionMapper; +import com.crowdin.cli.properties.ProjectProperties; +import com.crowdin.cli.utils.console.ConsoleSpinner; +import com.crowdin.client.branches.model.BranchMergeStatus; +import com.crowdin.client.branches.model.BranchMergeSummary; +import com.crowdin.client.branches.model.MergeBranchRequest; +import com.crowdin.client.projectsgroups.model.Type; +import com.crowdin.client.sourcefiles.model.Branch; +import lombok.AllArgsConstructor; + +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import static com.crowdin.cli.BaseCli.RESOURCE_BUNDLE; +import static com.crowdin.cli.utils.console.ExecutionStatus.OK; + +@AllArgsConstructor +class BranchMergeAction implements NewAction { + + private final String source; + private final String target; + private final boolean noProgress; + private final boolean plainView; + private final boolean dryrun; + private final boolean deleteAfterMerge; + + @Override + public void act(Outputter out, ProjectProperties properties, ProjectClient client) { + CrowdinProjectFull project = ConsoleSpinner.execute(out, "message.spinner.fetching_project_info", "error.collect_project_info", + this.noProgress, plainView, client::downloadFullProject); + + boolean isStringsBasedProject = Objects.equals(project.getType(), Type.STRINGS_BASED); + if (!isStringsBasedProject) { + throw new ExitCodeExceptionMapper.ValidationException(RESOURCE_BUNDLE.getString("error.string_based_only")); + } + + Optional sourceBranch = project.findBranchByName(source); + if (sourceBranch.isEmpty()) { + throw new ExitCodeExceptionMapper.NotFoundException(String.format(RESOURCE_BUNDLE.getString("error.branch_not_exists"), source)); + } + + Optional targetBranch = project.findBranchByName(target); + if (targetBranch.isEmpty()) { + throw new ExitCodeExceptionMapper.NotFoundException(String.format(RESOURCE_BUNDLE.getString("error.branch_not_exists"), target)); + } + + MergeBranchRequest request = new MergeBranchRequest(); + request.setSourceBranchId(sourceBranch.get().getId()); + request.setDeleteAfterMerge(deleteAfterMerge); + request.setDryRun(dryrun); + BranchMergeSummary summary = mergeBranch(out, client, targetBranch.get().getId(), request); + + String summaryStr = summary.getDetails().entrySet().stream() + .map(entry -> entry.getKey() + ": " + entry.getValue()) + .collect(Collectors.joining(", ")); + + if (!plainView) { + out.println(OK.withIcon(String.format(RESOURCE_BUNDLE.getString("message.branch.merge"), source, target))); + out.println(String.format(RESOURCE_BUNDLE.getString("message.branch.merge_details"), summaryStr)); + } else { + out.println(String.valueOf(summary.getTargetBranchId())); + } + } + + private BranchMergeSummary mergeBranch(Outputter out, ProjectClient client, Long branchId, MergeBranchRequest request) { + return ConsoleSpinner.execute( + out, + "message.spinner.merging_branch", + "error.branch.merge", + this.noProgress, + false, + () -> { + BranchMergeStatus status = client.mergeBranch(branchId, request); + + while (!status.getStatus().equalsIgnoreCase("finished")) { + ConsoleSpinner.update( + String.format(RESOURCE_BUNDLE.getString("message.spinner.merging_branch_percents"), status.getProgress())); + Thread.sleep(1000); + + status = client.checkMergeBranchStatus(branchId, status.getIdentifier()); + + if (status.getStatus().equalsIgnoreCase("failed")) { + throw new RuntimeException(RESOURCE_BUNDLE.getString("error.branch.merge")); + } + } + ConsoleSpinner.update(String.format(RESOURCE_BUNDLE.getString("message.spinner.merging_branch_percents"), 100)); + return client.getBranchMergeSummary(branchId, status.getIdentifier()); + } + ); + } +} diff --git a/src/main/java/com/crowdin/cli/commands/actions/CliActions.java b/src/main/java/com/crowdin/cli/commands/actions/CliActions.java index d784a3d5..47e0b16b 100644 --- a/src/main/java/com/crowdin/cli/commands/actions/CliActions.java +++ b/src/main/java/com/crowdin/cli/commands/actions/CliActions.java @@ -239,6 +239,11 @@ public NewAction branchClone(String source, St return new BranchCloneAction(source, target, noProgress, plainView); } + @Override + public NewAction branchMerge(String source, String target, boolean dryrun, boolean deleteAfterMerge, boolean noProgress, boolean plainView) { + return new BranchMergeAction(source, target, noProgress, plainView, dryrun, deleteAfterMerge); + } + @Override public NewAction branchDelete(String name) { return new BranchDeleteAction(name); diff --git a/src/main/java/com/crowdin/cli/commands/picocli/BranchMergeSubcommand.java b/src/main/java/com/crowdin/cli/commands/picocli/BranchMergeSubcommand.java new file mode 100644 index 00000000..61016b06 --- /dev/null +++ b/src/main/java/com/crowdin/cli/commands/picocli/BranchMergeSubcommand.java @@ -0,0 +1,36 @@ +package com.crowdin.cli.commands.picocli; + +import com.crowdin.cli.client.ProjectClient; +import com.crowdin.cli.commands.Actions; +import com.crowdin.cli.commands.NewAction; +import com.crowdin.cli.properties.ProjectProperties; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Parameters; + +@Command( + sortOptions = false, + name = CommandNames.BRANCH_MERGE +) +class BranchMergeSubcommand extends ActCommandProject { + + @Parameters(descriptionKey = "crowdin.branch.merge.source") + protected String source; + + @Parameters(descriptionKey = "crowdin.branch.merge.target") + protected String target; + + @CommandLine.Option(names = {"--dryrun"}, descriptionKey = "crowdin.branch.merge.dryrun", order = -2) + protected boolean dryrun; + + @CommandLine.Option(names = {"--delete-after-merge"}, descriptionKey = "crowdin.branch.merge.delete-after-merge", order = -2) + protected boolean deleteAfterMerge; + + @CommandLine.Option(names = {"--plain"}, descriptionKey = "crowdin.list.usage.plain") + protected boolean plainView; + + @Override + protected NewAction getAction(Actions actions) { + return actions.branchMerge(source, target, dryrun, deleteAfterMerge, noProgress, plainView); + } +} diff --git a/src/main/java/com/crowdin/cli/commands/picocli/BranchSubcommand.java b/src/main/java/com/crowdin/cli/commands/picocli/BranchSubcommand.java index bf22f097..c258b9ac 100644 --- a/src/main/java/com/crowdin/cli/commands/picocli/BranchSubcommand.java +++ b/src/main/java/com/crowdin/cli/commands/picocli/BranchSubcommand.java @@ -8,7 +8,8 @@ BranchAddSubcommand.class, BranchCloneSubcommand.class, BranchDeleteSubcommand.class, - BranchListSubcommand.class + BranchListSubcommand.class, + BranchMergeSubcommand.class } ) class BranchSubcommand extends HelpCommand { diff --git a/src/main/java/com/crowdin/cli/commands/picocli/CommandNames.java b/src/main/java/com/crowdin/cli/commands/picocli/CommandNames.java index d7a86e05..ec2fd1b6 100644 --- a/src/main/java/com/crowdin/cli/commands/picocli/CommandNames.java +++ b/src/main/java/com/crowdin/cli/commands/picocli/CommandNames.java @@ -19,10 +19,10 @@ public final class CommandNames { public static final String STRING = "string"; public static final String STRING_EDIT = "edit"; - public static final String STRING_COMMENT = "comment"; public static final String BRANCH = "branch"; public static final String BRANCH_CLONE = "clone"; + public static final String BRANCH_MERGE = "merge"; public static final String GLOSSARY = "glossary"; public static final String GLOSSARY_UPLOAD = "upload"; diff --git a/src/main/resources/messages/messages.properties b/src/main/resources/messages/messages.properties index c15d16da..79dbb8e8 100755 --- a/src/main/resources/messages/messages.properties +++ b/src/main/resources/messages/messages.properties @@ -239,6 +239,14 @@ crowdin.branch.clone.usage.customSynopsis=@|fg(green) crowdin branch clone|@ [CONFIG OPTIONS] [OPTIONS] +crowdin.branch.merge.source=Source branch name +crowdin.branch.merge.target=Target branch name +crowdin.branch.merge.dryrun=Simulate merging without making any real changes +crowdin.branch.merge.delete-after-merge=Whether to delete branch after merge + crowdin.glossary.upload.usage.description=Upload glossary to localization resources crowdin.glossary.upload.usage.customSynopsis=@|fg(green) crowdin glossary upload|@ [CONFIG OPTIONS] [OPTIONS] crowdin.glossary.upload.file=File to upload @@ -574,6 +582,7 @@ error.file.dest_required='dest' parameter required to specify source file path error.file.type_required='--type' is required for '--parser-version' option error.branch.clone=Failed to clone the branch +error.branch.merge=Failed to merge the branch error.response.401=Couldn't authorize. Check your 'api_token' error.response.403=You do not have permission to view/edit project with provided id @@ -673,6 +682,8 @@ message.download_translations.preserve_hierarchy_warning=Because the 'preserve_h message.language.list=@|yellow %s|@ @|green %s|@ message.branch.list=@|yellow #%d|@ @|green %s|@ +message.branch.merge=@|green Merged branch '%s' into '%s'|@ +message.branch.merge_details=\tMerge summary: %s message.file.list=@|yellow #%d|@ @|green %s|@ message.file.list_verbose=@|yellow #%d|@ @|green %s|@ %s @@ -778,7 +789,9 @@ message.spinner.uploading_strings=Uploading strings message.spinner.uploading_strings_percents=Uploading strings (%d%%) message.spinner.upload_strings_failed=Upload has failed message.spinner.cloning_branch=Cloning branch +message.spinner.merging_branch=Merging branch message.spinner.cloning_branch_percents=Cloning branch @|bold (%d%%)|@ +message.spinner.merging_branch_percents=Merging branch @|bold (%d%%)|@ message.faq_link=Visit the @|cyan https://crowdin.github.io/crowdin-cli/faq|@ for more details message.translations_build_unsuccessful=Didn't manage to build translations diff --git a/src/test/java/com/crowdin/cli/commands/actions/BranchMergeActionTest.java b/src/test/java/com/crowdin/cli/commands/actions/BranchMergeActionTest.java new file mode 100644 index 00000000..22c88a38 --- /dev/null +++ b/src/test/java/com/crowdin/cli/commands/actions/BranchMergeActionTest.java @@ -0,0 +1,108 @@ +package com.crowdin.cli.commands.actions; + +import com.crowdin.cli.client.CrowdinProjectFull; +import com.crowdin.cli.client.ProjectBuilder; +import com.crowdin.cli.client.ProjectClient; +import com.crowdin.cli.client.ResponseException; +import com.crowdin.cli.commands.Outputter; +import com.crowdin.cli.properties.NewPropertiesWithFilesUtilBuilder; +import com.crowdin.cli.properties.PropertiesWithFiles; +import com.crowdin.cli.properties.helper.FileHelperTest; +import com.crowdin.cli.properties.helper.TempProject; +import com.crowdin.cli.utils.Utils; +import com.crowdin.client.branches.model.BranchMergeStatus; +import com.crowdin.client.branches.model.BranchMergeSummary; +import com.crowdin.client.branches.model.MergeBranchRequest; +import com.crowdin.client.projectsgroups.model.Type; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +class BranchMergeActionTest { + + private TempProject project; + + @BeforeEach + public void createProj() { + project = new TempProject(FileHelperTest.class); + } + + @AfterEach + public void deleteProj() { + project.delete(); + } + + @Test + public void testBranchMerge() throws ResponseException { + NewPropertiesWithFilesUtilBuilder pbBuilder = NewPropertiesWithFilesUtilBuilder + .minimalBuiltPropertiesBean("*", Utils.PATH_SEPARATOR + "%original_file_name%-CR-%locale%") + .setBasePath(Utils.PATH_SEPARATOR); + PropertiesWithFiles pb = pbBuilder.build(); + ProjectClient client = mock(ProjectClient.class); + long mainBranch = 14L; + long devBranch = 15L; + String mergeId = "50fb3506-4127-4ba8-8296-f97dc7e3e0c3"; + CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())) + .addBranches(mainBranch, "main", "dev") + .build(); + build.setType(Type.STRINGS_BASED); + + MergeBranchRequest merged = new MergeBranchRequest() {{ + setSourceBranchId(devBranch); + setDryRun(true); + setDeleteAfterMerge(false); + }}; + BranchMergeStatus initStatus = new BranchMergeStatus() {{ + setIdentifier(mergeId); + setStatus("created"); + setProgress(0); + }}; + BranchMergeStatus status = new BranchMergeStatus() {{ + setIdentifier(mergeId); + setStatus("finished"); + setProgress(100); + }}; + BranchMergeSummary summary = new BranchMergeSummary() {{ + setSourceBranchId(devBranch); + setDetails(Map.of("added", 1L, "deleted", 0L, "updated", 0L, "conflicted", 0L)); + }}; + + when(client.downloadFullProject()).thenReturn(build); + when(client.mergeBranch(eq(mainBranch), eq(merged))).thenReturn(initStatus); + when(client.checkMergeBranchStatus(eq(mainBranch), eq(mergeId))) + .thenReturn(status); + when(client.getBranchMergeSummary(eq(mainBranch), eq(mergeId))) + .thenReturn(summary); + + BranchMergeAction action = new BranchMergeAction("dev", "main", false, false, true, false); + action.act(Outputter.getDefault(), pb, client); + verify(client).downloadFullProject(); + verify(client).mergeBranch(eq(mainBranch), eq(merged)); + verify(client).checkMergeBranchStatus(eq(mainBranch), eq(mergeId)); + verify(client).getBranchMergeSummary(eq(mainBranch), eq(mergeId)); + verifyNoMoreInteractions(client); + } + + @Test + public void testBranchMerge_FileBasedThrows() { + NewPropertiesWithFilesUtilBuilder pbBuilder = NewPropertiesWithFilesUtilBuilder + .minimalBuiltPropertiesBean("*", Utils.PATH_SEPARATOR + "%original_file_name%-CR-%locale%") + .setBasePath(Utils.PATH_SEPARATOR); + PropertiesWithFiles pb = pbBuilder.build(); + ProjectClient client = mock(ProjectClient.class); + CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())) + .addBranches(14L, "main").build(); + build.setType(Type.FILES_BASED); + BranchMergeAction action = new BranchMergeAction("main", "cloned", false, false, true, false); + + assertThrows(RuntimeException.class, () -> action.act(Outputter.getDefault(), pb, client)); + + verify(client).downloadFullProject(); + verifyNoMoreInteractions(client); + } +} \ No newline at end of file diff --git a/src/test/java/com/crowdin/cli/commands/picocli/BranchMergeSubcommandTest.java b/src/test/java/com/crowdin/cli/commands/picocli/BranchMergeSubcommandTest.java new file mode 100644 index 00000000..8c09fbba --- /dev/null +++ b/src/test/java/com/crowdin/cli/commands/picocli/BranchMergeSubcommandTest.java @@ -0,0 +1,17 @@ +package com.crowdin.cli.commands.picocli; + +import org.junit.jupiter.api.Test; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.verify; + +class BranchMergeSubcommandTest extends PicocliTestUtils { + + @Test + public void testBranchMerge() { + this.execute(CommandNames.BRANCH, CommandNames.BRANCH_MERGE, "main", "dev"); + verify(actionsMock).branchMerge(any(), any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean()); + this.check(true); + } +} \ No newline at end of file diff --git a/src/test/java/com/crowdin/cli/commands/picocli/PicocliTestUtils.java b/src/test/java/com/crowdin/cli/commands/picocli/PicocliTestUtils.java index dfed3417..2b1f992e 100644 --- a/src/test/java/com/crowdin/cli/commands/picocli/PicocliTestUtils.java +++ b/src/test/java/com/crowdin/cli/commands/picocli/PicocliTestUtils.java @@ -138,6 +138,8 @@ void mockActions() { when(actionsMock.projectList(anyBoolean())).thenReturn(actionMock); when(actionsMock.branchClone(any(), any(), anyBoolean(), anyBoolean())) .thenReturn(actionMock); + when(actionsMock.branchMerge(any(), any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean())) + .thenReturn(actionMock); } private void mockBuilders() { diff --git a/website/blog/2024-05-28-cli-v4/index.md b/website/blog/2024-05-28-cli-v4/index.md index 32e37a3b..62144359 100644 --- a/website/blog/2024-05-28-cli-v4/index.md +++ b/website/blog/2024-05-28-cli-v4/index.md @@ -25,6 +25,7 @@ As part of this release, the minimum Java version has been updated to **17 LTS** - [`crowdin project`](/commands/crowdin-project) - [`crowdin language`](/commands/crowdin-language) - [`crowdin branch clone`](/commands/crowdin-branch-clone) +- [`crowdin branch merge`](/commands/crowdin-branch-merge) - [`crowdin completion`](/autocompletion) ## Command updates diff --git a/website/mantemplates/crowdin-branch-merge.adoc b/website/mantemplates/crowdin-branch-merge.adoc new file mode 100644 index 00000000..76632eb5 --- /dev/null +++ b/website/mantemplates/crowdin-branch-merge.adoc @@ -0,0 +1,20 @@ +:includedir: ../generated-picocli-docs +:command: crowdin-branch-merge + +== crowdin branch merge + +include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-description] + +include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-synopsis] + +include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-arguments] + +include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-commands] + +include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-options] + +include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-footer] + +=== Notes + +- This command is only available for link:https://support.crowdin.com/creating-project/#project-types[string-based] projects. diff --git a/website/sidebars.js b/website/sidebars.js index 9a3476d2..5313b52e 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -113,6 +113,7 @@ const sidebars = { 'commands/crowdin-branch-add', 'commands/crowdin-branch-delete', 'commands/crowdin-branch-clone', + 'commands/crowdin-branch-merge', ] }, {