chore: git detach (#37934)

## Description
> [!TIP]  
> _Add a TL;DR when the description is longer than 500 words or
extremely technical (helps the content, marketing, and DevRel team)._
>
> _Please also include relevant motivation and context. List any
dependencies that are required for this change. Add links to Notion,
Figma or any other documents that might be relevant to the PR._


Fixes #37439 

## Automation

/ok-to-test tags="@tag.Git"




## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced methods for committing artifacts and managing Git
operations, including `commitArtifact`, `detachRemote`, and
`publishArtifactPostCommit`.
  - Added functionality to list branches and check repository privacy.
- Enhanced error handling for Git operations, improving reliability
during commits and pushes.

- **Bug Fixes**
- Updated parameter naming for consistency across methods, enhancing
clarity.

- **Documentation**
- Improved JavaDoc comments for better understanding of method purposes
and parameters.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/12158276745>
> Commit: f058fbe37f866ecee2cfb8fde30e4d4b62e2fd7f
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12158276745&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Git`
> Spec:
> <hr>Wed, 04 Dec 2024 11:42:39 UTC
<!-- end of auto-generated comment: Cypress test results  -->
This commit is contained in:
Manish Kumar 2024-12-05 10:35:44 +05:30 committed by GitHub
parent 9c669ddcb8
commit a64c16d98a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 112 additions and 7 deletions

View File

@ -21,4 +21,6 @@ public interface CentralGitServiceCE {
Mono<String> commitArtifact(
CommitDTO commitDTO, String branchedArtifactId, ArtifactType artifactType, GitType gitType);
Mono<? extends Artifact> detachRemote(String branchedArtifactId, ArtifactType artifactType, GitType gitType);
}

View File

@ -2,6 +2,7 @@ package com.appsmith.server.git.central;
import com.appsmith.external.constants.AnalyticsEvents;
import com.appsmith.external.git.constants.GitConstants;
import com.appsmith.external.git.constants.GitSpan;
import com.appsmith.external.models.Datasource;
import com.appsmith.external.models.DatasourceStorage;
import com.appsmith.git.dto.CommitDTO;
@ -47,8 +48,10 @@ import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import reactor.core.observability.micrometer.Micrometer;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuple3;
import java.io.IOException;
import java.time.Instant;
@ -571,7 +574,7 @@ public class CentralGitServiceCEImpl implements CentralGitServiceCE {
return this.commitArtifact(commitDTO, artifact.getId(), artifactType, gitType)
.onErrorResume(error ->
// If the push fails remove all the cloned files from local repo
this.detachRemote(baseArtifactId, artifactType)
this.detachRemote(baseArtifactId, artifactType, gitType)
.flatMap(isDeleted -> {
if (error instanceof TransportException) {
return gitAnalyticsUtils
@ -852,13 +855,81 @@ public class CentralGitServiceCEImpl implements CentralGitServiceCE {
}
/**
* TODO: implementation quite similar to the disconnectGitRepo
* @param baseArtifactId
* @param artifactType
* @return
* Method to remove all the git metadata for the artifact and connected resources. This will remove:
* - local repo
* - all the branched applications present in DB except for default application
*
* @param branchedArtifactId : id of any branched artifact for the given repo
* @param artifactType : type of artifact
* @return : the base artifact after removal of git flow.
*/
protected Mono<? extends Artifact> detachRemote(String baseArtifactId, ArtifactType artifactType) {
return null;
public Mono<? extends Artifact> detachRemote(
String branchedArtifactId, ArtifactType artifactType, GitType gitType) {
GitHandlingService gitHandlingService = gitHandlingServiceResolver.getGitHandlingService(gitType);
GitArtifactHelper<?> gitArtifactHelper = gitArtifactHelperResolver.getArtifactHelper(artifactType);
AclPermission gitConnectPermission = gitArtifactHelper.getArtifactGitConnectPermission();
Mono<Tuple2<? extends Artifact, ? extends Artifact>> baseAndBranchedArtifactMono =
getBaseAndBranchedArtifacts(branchedArtifactId, artifactType, gitConnectPermission);
Mono<? extends Artifact> disconnectMono = baseAndBranchedArtifactMono
.flatMap(artifactTuples -> {
Artifact baseArtifact = artifactTuples.getT1();
if (isBaseGitMetadataInvalid(baseArtifact.getGitArtifactMetadata(), gitType)) {
return Mono.error(new AppsmithException(
AppsmithError.INVALID_GIT_CONFIGURATION,
"Please reconfigure the artifact to connect to git repo"));
}
GitArtifactMetadata gitArtifactMetadata = baseArtifact.getGitArtifactMetadata();
ArtifactJsonTransformationDTO jsonTransformationDTO = new ArtifactJsonTransformationDTO();
jsonTransformationDTO.setRefType(RefType.BRANCH);
jsonTransformationDTO.setWorkspaceId(baseArtifact.getWorkspaceId());
jsonTransformationDTO.setBaseArtifactId(gitArtifactMetadata.getDefaultArtifactId());
jsonTransformationDTO.setRepoName(gitArtifactMetadata.getRepoName());
jsonTransformationDTO.setArtifactType(baseArtifact.getArtifactType());
jsonTransformationDTO.setRefName(gitArtifactMetadata.getBranchName());
// Remove the git contents from file system
return Mono.zip(gitHandlingService.listBranches(jsonTransformationDTO), Mono.just(baseArtifact));
})
.flatMap(tuple -> {
List<String> localBranches = tuple.getT1();
Artifact baseArtifact = tuple.getT2();
baseArtifact.setGitArtifactMetadata(null);
gitArtifactHelper.resetAttributeInBaseArtifact(baseArtifact);
GitArtifactMetadata gitArtifactMetadata = baseArtifact.getGitArtifactMetadata();
ArtifactJsonTransformationDTO jsonTransformationDTO = new ArtifactJsonTransformationDTO();
jsonTransformationDTO.setRefType(RefType.BRANCH);
jsonTransformationDTO.setWorkspaceId(baseArtifact.getWorkspaceId());
jsonTransformationDTO.setBaseArtifactId(gitArtifactMetadata.getDefaultArtifactId());
jsonTransformationDTO.setRepoName(gitArtifactMetadata.getRepoName());
jsonTransformationDTO.setArtifactType(baseArtifact.getArtifactType());
jsonTransformationDTO.setRefName(gitArtifactMetadata.getBranchName());
// Remove the parent application branch name from the list
Mono<Boolean> removeRepoMono = gitHandlingService.removeRepository(jsonTransformationDTO);
Mono<? extends Artifact> updatedArtifactMono = gitArtifactHelper.saveArtifact(baseArtifact);
Flux<? extends Artifact> deleteAllBranchesFlux =
gitArtifactHelper.deleteAllBranches(branchedArtifactId, localBranches);
return Mono.zip(updatedArtifactMono, removeRepoMono, deleteAllBranchesFlux.collectList())
.map(Tuple3::getT1);
})
.flatMap(updatedBaseArtifact -> {
return gitArtifactHelper
.disconnectEntitiesOfBaseArtifact(updatedBaseArtifact)
.then(gitAnalyticsUtils.addAnalyticsForGitOperation(
AnalyticsEvents.GIT_DISCONNECT, updatedBaseArtifact, false));
})
.name(GitSpan.OPS_DETACH_REMOTE)
.tap(Micrometer.observation(observationRegistry));
return Mono.create(sink -> disconnectMono.subscribe(sink::success, sink::error, null, sink.currentContext()));
}
private boolean isBaseGitMetadataInvalid(GitArtifactMetadata gitArtifactMetadata, GitType gitType) {

View File

@ -10,6 +10,7 @@ import com.appsmith.server.git.dtos.ArtifactJsonTransformationDTO;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import java.util.List;
import java.util.Set;
public interface GitHandlingServiceCE {
@ -38,6 +39,8 @@ public interface GitHandlingServiceCE {
Mono<Boolean> removeRepository(ArtifactJsonTransformationDTO artifactJsonTransformationDTO);
Mono<List<String>> listBranches(ArtifactJsonTransformationDTO artifactJsonTransformationDTO);
Mono<Boolean> validateEmptyRepository(ArtifactJsonTransformationDTO artifactJsonTransformationDTO);
Mono<Boolean> initialiseReadMe(

View File

@ -1,6 +1,7 @@
package com.appsmith.server.git.fs;
import com.appsmith.external.constants.AnalyticsEvents;
import com.appsmith.external.dtos.GitBranchDTO;
import com.appsmith.external.git.constants.GitConstants;
import com.appsmith.external.git.constants.GitSpan;
import com.appsmith.external.git.handler.FSGitHandler;
@ -49,12 +50,14 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.reactive.TransactionalOperator;
import org.springframework.util.StringUtils;
import reactor.core.observability.micrometer.Micrometer;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeoutException;
@ -248,6 +251,32 @@ public class GitFSServiceCEImpl implements GitHandlingServiceCE {
return commonGitFileUtils.deleteLocalRepo(repoSuffix);
}
/**
* List all the local branches present in the file system
* @param artifactJsonTransformationDTO
* @return
*/
@Override
public Mono<List<String>> listBranches(ArtifactJsonTransformationDTO artifactJsonTransformationDTO) {
GitArtifactHelper<?> gitArtifactHelper =
gitArtifactHelperResolver.getArtifactHelper(artifactJsonTransformationDTO.getArtifactType());
Path repoSuffix = gitArtifactHelper.getRepoSuffixPath(
artifactJsonTransformationDTO.getWorkspaceId(),
artifactJsonTransformationDTO.getBaseArtifactId(),
artifactJsonTransformationDTO.getRepoName());
return fsGitHandler
.listBranches(repoSuffix)
.flatMapMany(Flux::fromIterable)
.filter(gitBranchDTO -> {
return StringUtils.hasText(gitBranchDTO.getBranchName())
&& !gitBranchDTO.getBranchName().startsWith("origin");
})
.map(GitBranchDTO::getBranchName)
.collectList();
}
@Override
public Mono<Boolean> validateEmptyRepository(ArtifactJsonTransformationDTO artifactJsonTransformationDTO) {
GitArtifactHelper<?> gitArtifactHelper =