diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ce/GitArtifactMetadataCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ce/GitArtifactMetadataCE.java index 1c3b700c56..f5b038489c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ce/GitArtifactMetadataCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ce/GitArtifactMetadataCE.java @@ -2,6 +2,7 @@ package com.appsmith.server.domains.ce; import com.appsmith.external.models.AppsmithDomain; import com.appsmith.external.views.Views; +import com.appsmith.server.constants.ce.RefType; import com.appsmith.server.domains.AutoCommitConfig; import com.appsmith.server.domains.GitAuth; import com.appsmith.server.domains.GitProfile; @@ -24,6 +25,16 @@ public class GitArtifactMetadataCE implements AppsmithDomain { @JsonView(Views.Public.class) String branchName; + // TODO: make this public view and remove transient annotation once implmentation completes + @Transient + @JsonView(Views.Internal.class) + String refName; + + // TODO: make this public view and remove transient annotation once implementation completes + @Transient + @JsonView(Views.Internal.class) + RefType refType; + // Git default branch corresponding to the remote git repo to which the application is connected to @JsonView(Views.Public.class) String defaultBranchName; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCE.java index 27768442c0..576a7fb99c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCE.java @@ -2,6 +2,7 @@ package com.appsmith.server.git.central; import com.appsmith.git.dto.CommitDTO; import com.appsmith.server.constants.ArtifactType; +import com.appsmith.server.constants.ce.RefType; import com.appsmith.server.domains.Artifact; import com.appsmith.server.dtos.ArtifactImportDTO; import com.appsmith.server.dtos.GitConnectDTO; @@ -23,4 +24,11 @@ public interface CentralGitServiceCE { CommitDTO commitDTO, String branchedArtifactId, ArtifactType artifactType, GitType gitType); Mono detachRemote(String branchedArtifactId, ArtifactType artifactType, GitType gitType); + + Mono fetchRemoteChanges( + String referenceArtifactId, + boolean isFileLock, + ArtifactType artifactType, + GitType gitType, + RefType refType); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCECompatibleImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCECompatibleImpl.java index b996e58491..f33fa75b12 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCECompatibleImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCECompatibleImpl.java @@ -10,6 +10,7 @@ import com.appsmith.server.git.utils.GitProfileUtils; import com.appsmith.server.helpers.GitPrivateRepoHelper; import com.appsmith.server.imports.internal.ImportService; import com.appsmith.server.plugins.base.PluginService; +import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.UserDataService; import com.appsmith.server.services.WorkspaceService; import com.appsmith.server.solutions.DatasourcePermission; @@ -26,6 +27,7 @@ public class CentralGitServiceCECompatibleImpl extends CentralGitServiceCEImpl GitProfileUtils gitProfileUtils, GitAnalyticsUtils gitAnalyticsUtils, UserDataService userDataService, + SessionUserService sessionUserService, GitArtifactHelperResolver gitArtifactHelperResolver, GitHandlingServiceResolver gitHandlingServiceResolver, GitPrivateRepoHelper gitPrivateRepoHelper, @@ -41,6 +43,7 @@ public class CentralGitServiceCECompatibleImpl extends CentralGitServiceCEImpl gitProfileUtils, gitAnalyticsUtils, userDataService, + sessionUserService, gitArtifactHelperResolver, gitHandlingServiceResolver, gitPrivateRepoHelper, diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCEImpl.java index 9af8b9ce9e..64bf970ce9 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCEImpl.java @@ -18,6 +18,7 @@ import com.appsmith.server.domains.GitArtifactMetadata; import com.appsmith.server.domains.GitAuth; import com.appsmith.server.domains.GitProfile; import com.appsmith.server.domains.Plugin; +import com.appsmith.server.domains.User; import com.appsmith.server.domains.UserData; import com.appsmith.server.domains.Workspace; import com.appsmith.server.dtos.ArtifactExchangeJson; @@ -36,6 +37,7 @@ import com.appsmith.server.helpers.GitPrivateRepoHelper; import com.appsmith.server.imports.internal.ImportService; import com.appsmith.server.plugins.base.PluginService; import com.appsmith.server.services.GitArtifactHelper; +import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.UserDataService; import com.appsmith.server.services.WorkspaceService; import com.appsmith.server.solutions.DatasourcePermission; @@ -44,6 +46,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.api.errors.TransportException; +import org.eclipse.jgit.lib.BranchTrackingStatus; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -65,6 +68,7 @@ import static com.appsmith.external.git.constants.ce.GitConstantsCE.GIT_CONFIG_E import static com.appsmith.external.git.constants.ce.GitConstantsCE.GIT_PROFILE_ERROR; import static com.appsmith.external.git.constants.ce.GitSpanCE.OPS_COMMIT; import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNestedNonNullProperties; +import static com.appsmith.server.constants.FieldName.BRANCH_NAME; import static com.appsmith.server.constants.FieldName.DEFAULT; import static com.appsmith.server.constants.SerialiseArtifactObjective.VERSION_CONTROL; import static java.lang.Boolean.FALSE; @@ -79,6 +83,7 @@ public class CentralGitServiceCEImpl implements CentralGitServiceCE { private final GitProfileUtils gitProfileUtils; private final GitAnalyticsUtils gitAnalyticsUtils; private final UserDataService userDataService; + private final SessionUserService sessionUserService; protected final GitArtifactHelperResolver gitArtifactHelperResolver; protected final GitHandlingServiceResolver gitHandlingServiceResolver; @@ -940,6 +945,108 @@ public class CentralGitServiceCEImpl implements CentralGitServiceCE { .isGitAuthInvalid(gitArtifactMetadata.getGitAuth()); } + public Mono fetchRemoteChanges( + Artifact baseArtifact, Artifact refArtifact, boolean isFileLock, GitType gitType, RefType refType) { + + if (refArtifact == null + || baseArtifact == null + || isBaseGitMetadataInvalid(baseArtifact.getGitArtifactMetadata(), gitType)) { + return Mono.error(new AppsmithException(AppsmithError.GIT_GENERIC_ERROR)); + } + + GitArtifactMetadata baseArtifactGitData = baseArtifact.getGitArtifactMetadata(); + GitArtifactMetadata refArtifactGitData = refArtifact.getGitArtifactMetadata(); + + String baseArtifactId = baseArtifactGitData.getDefaultArtifactId(); + + // TODO add gitType in all error messages. + if (refArtifactGitData == null || !hasText(refArtifactGitData.getRefName())) { + return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, BRANCH_NAME)); + } + + Mono currUserMono = sessionUserService.getCurrentUser().cache(); // will be used to send analytics event + Mono acquireGitLockMono = + gitRedisUtils.acquireGitLock(baseArtifactId, GitConstants.GitCommandConstants.FETCH_REMOTE, isFileLock); + + ArtifactJsonTransformationDTO jsonTransformationDTO = new ArtifactJsonTransformationDTO(); + jsonTransformationDTO.setWorkspaceId(baseArtifact.getWorkspaceId()); + jsonTransformationDTO.setBaseArtifactId(baseArtifactGitData.getDefaultArtifactId()); + jsonTransformationDTO.setRepoName(baseArtifactGitData.getRepoName()); + jsonTransformationDTO.setArtifactType(baseArtifact.getArtifactType()); + jsonTransformationDTO.setRefName(refArtifactGitData.getRefName()); + jsonTransformationDTO.setRefType(refType); + + GitHandlingService gitHandlingService = gitHandlingServiceResolver.getGitHandlingService(gitType); + + // current user mono has been zipped just to run in parallel. + Mono fetchRemoteMono = acquireGitLockMono + .then(Mono.defer(() -> + gitHandlingService.fetchRemoteChanges(jsonTransformationDTO, baseArtifactGitData.getGitAuth()))) + .flatMap(fetchedRemoteStatusString -> { + return gitRedisUtils.releaseFileLock(baseArtifactId).thenReturn(fetchedRemoteStatusString); + }) + .onErrorResume(throwable -> { + /* + in case of any error, the global exception handler will release the lock + hence we don't need to do that manually + */ + log.error( + "Error to fetch from remote for application: {}, branch: {}, git type {}", + baseArtifactId, + refArtifactGitData.getRefName(), + gitType, + throwable); + return Mono.error( + new AppsmithException(AppsmithError.GIT_ACTION_FAILED, "fetch", throwable.getMessage())); + }) + .elapsed() + .zipWith(currUserMono) + .flatMap(objects -> { + Long elapsedTime = objects.getT1().getT1(); + String fetchRemote = objects.getT1().getT2(); + User currentUser = objects.getT2(); + return gitAnalyticsUtils + .sendUnitExecutionTimeAnalyticsEvent( + AnalyticsEvents.GIT_FETCH.getEventName(), elapsedTime, currentUser, refArtifact) + .thenReturn(fetchRemote); + }) + .name(GitSpan.OPS_FETCH_REMOTE) + .tap(Micrometer.observation(observationRegistry)); + + return Mono.create(sink -> { + fetchRemoteMono.subscribe(sink::success, sink::error, null, sink.currentContext()); + }); + } + + /** + * This method is responsible to compare the current branch with the remote branch. + * Comparing means finding two numbers - how many commits ahead and behind the local branch is. + * It'll do the following things - + * 1. Checkout (if required) to the branch to make sure we are comparing the right branch + * 2. Run a git fetch command to fetch the latest changes from the remote + * + * @param refArtifactId id of the reference + * @param isFileLock whether to add file lock or not + * @param artifactType + * @return Mono of {@link BranchTrackingStatus} + */ + @Override + public Mono fetchRemoteChanges( + String refArtifactId, boolean isFileLock, ArtifactType artifactType, GitType gitType, RefType refType) { + GitArtifactHelper artifactGitHelper = gitArtifactHelperResolver.getArtifactHelper(artifactType); + AclPermission artifactEditPermission = artifactGitHelper.getArtifactEditPermission(); + + Mono> baseAndBranchedArtifactMono = + getBaseAndBranchedArtifacts(refArtifactId, artifactType, artifactEditPermission); + + return baseAndBranchedArtifactMono.flatMap(artifactTuples -> { + Artifact baseArtifact = artifactTuples.getT1(); + Artifact refArtifact = artifactTuples.getT2(); + + return fetchRemoteChanges(baseArtifact, refArtifact, isFileLock, gitType, refType); + }); + } + /** * Returns baseArtifact and branchedArtifact * This operation is quite frequently used, hence providing the right set diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceImpl.java index 7c642d832d..0a85050f1f 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceImpl.java @@ -10,6 +10,7 @@ import com.appsmith.server.git.utils.GitProfileUtils; import com.appsmith.server.helpers.GitPrivateRepoHelper; import com.appsmith.server.imports.internal.ImportService; import com.appsmith.server.plugins.base.PluginService; +import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.UserDataService; import com.appsmith.server.services.WorkspaceService; import com.appsmith.server.solutions.DatasourcePermission; @@ -25,6 +26,7 @@ public class CentralGitServiceImpl extends CentralGitServiceCECompatibleImpl imp GitProfileUtils gitProfileUtils, GitAnalyticsUtils gitAnalyticsUtils, UserDataService userDataService, + SessionUserService sessionUserService, GitArtifactHelperResolver gitArtifactHelperResolver, GitHandlingServiceResolver gitHandlingServiceResolver, GitPrivateRepoHelper gitPrivateRepoHelper, @@ -40,6 +42,7 @@ public class CentralGitServiceImpl extends CentralGitServiceCECompatibleImpl imp gitProfileUtils, gitAnalyticsUtils, userDataService, + sessionUserService, gitArtifactHelperResolver, gitHandlingServiceResolver, gitPrivateRepoHelper, diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/GitHandlingServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/GitHandlingServiceCE.java index 3b79a0f179..5b9551ffa6 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/GitHandlingServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/GitHandlingServiceCE.java @@ -56,4 +56,6 @@ public interface GitHandlingServiceCE { Mono> commitArtifact( Artifact branchedArtifact, CommitDTO commitDTO, ArtifactJsonTransformationDTO jsonTransformationDTO); + + Mono fetchRemoteChanges(ArtifactJsonTransformationDTO jsonTransformationDTO, GitAuth gitAuth); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/fs/GitFSServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/fs/GitFSServiceCEImpl.java index a65c46733c..ffd8c26e4f 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/fs/GitFSServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/fs/GitFSServiceCEImpl.java @@ -568,4 +568,31 @@ public class GitFSServiceCEImpl implements GitHandlingServiceCE { } return Mono.just(pushResult); } + + /** + * File system implementation of fetching remote changes. equivalent to git fetch + * @param jsonTransformationDTO : DTO to create path and other ref related details + * @param gitAuth : authentication holder + * @return : returns string for remote fetch + */ + @Override + public Mono fetchRemoteChanges(ArtifactJsonTransformationDTO jsonTransformationDTO, GitAuth gitAuth) { + + String workspaceId = jsonTransformationDTO.getWorkspaceId(); + String baseArtifactId = jsonTransformationDTO.getBaseArtifactId(); + String repoName = jsonTransformationDTO.getRepoName(); + String refName = jsonTransformationDTO.getRefName(); + + ArtifactType artifactType = jsonTransformationDTO.getArtifactType(); + GitArtifactHelper gitArtifactHelper = gitArtifactHelperResolver.getArtifactHelper(artifactType); + Path repoSuffix = gitArtifactHelper.getRepoSuffixPath(workspaceId, baseArtifactId, repoName); + + Path repoPath = fsGitHandler.createRepoPath(repoSuffix); + Mono checkoutBranchMono = fsGitHandler.checkoutToBranch(repoSuffix, refName); + + Mono fetchRemoteMono = fsGitHandler.fetchRemote( + repoPath, gitAuth.getPublicKey(), gitAuth.getPrivateKey(), true, refName, false); + + return checkoutBranchMono.then(Mono.defer(() -> fetchRemoteMono)); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/utils/GitAnalyticsUtils.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/utils/GitAnalyticsUtils.java index add712ac1c..1cc0f71d61 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/utils/GitAnalyticsUtils.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/utils/GitAnalyticsUtils.java @@ -5,6 +5,7 @@ import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.ApplicationMode; import com.appsmith.server.domains.Artifact; import com.appsmith.server.domains.GitArtifactMetadata; +import com.appsmith.server.domains.User; import com.appsmith.server.helpers.GitUtils; import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.SessionUserService; @@ -122,4 +123,27 @@ public class GitAnalyticsUtils { .sendEvent(event.getEventName(), user.getUsername(), analyticsProps) .thenReturn(artifact)); } + + public Mono sendUnitExecutionTimeAnalyticsEvent( + String flowName, Long elapsedTime, User currentUser, Artifact artifact) { + GitArtifactMetadata gitArtifactMetadata = artifact.getGitArtifactMetadata(); + + final Map data = Map.of( + FieldName.FLOW_NAME, + flowName, + FieldName.APPLICATION_ID, + gitArtifactMetadata.getDefaultArtifactId(), + "appId", + gitArtifactMetadata.getDefaultArtifactId(), + FieldName.BRANCH_NAME, + gitArtifactMetadata.getBranchName(), + "organizationId", + artifact.getWorkspaceId(), + "repoUrl", + gitArtifactMetadata.getRemoteUrl(), + "executionTime", + elapsedTime); + return analyticsService.sendEvent( + AnalyticsEvents.UNIT_EXECUTION_TIME.getEventName(), currentUser.getUsername(), data); + } }