chore: Refactor default branch codes (#27104)

## Description
This PR refactors the code related to list of branches and default
branch. It separates different parts of the code into separate
functions.

#### PR fixes following issue(s)
Fixes #26875
This commit is contained in:
Nayan 2023-09-14 10:08:36 +06:00 committed by GitHub
parent d92f29600b
commit fdfb13643a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 428 additions and 496 deletions

View File

@ -424,69 +424,59 @@ public class GitExecutorImpl implements GitExecutor {
}
@Override
public Mono<List<GitBranchDTO>> listBranches(
Path repoSuffix, String remoteUrl, String privateKey, String publicKey, Boolean refreshBranches) {
String gitAction = Boolean.TRUE.equals(refreshBranches)
? AnalyticsEvents.GIT_SYNC_BRANCH.getEventName()
: AnalyticsEvents.GIT_LIST_LOCAL_BRANCH.getEventName();
Stopwatch processStopwatch = StopwatchHelpers.startStopwatch(repoSuffix, gitAction);
;
public Mono<List<GitBranchDTO>> listBranches(Path repoSuffix) {
Path baseRepoPath = createRepoPath(repoSuffix);
return Mono.fromCallable(() -> {
log.debug(Thread.currentThread().getName() + ": Get branches for the application " + repoSuffix);
TransportConfigCallback transportConfigCallback =
new SshTransportConfigCallback(privateKey, publicKey);
Git git = Git.open(baseRepoPath.toFile());
List<Ref> refList = git.branchList()
.setListMode(ListBranchCommand.ListMode.ALL)
.call();
String defaultBranch = null;
if (Boolean.TRUE.equals(refreshBranches)) {
// Get default branch name from the remote
defaultBranch = git.lsRemote()
.setRemote(remoteUrl)
.setTransportConfigCallback(transportConfigCallback)
.callAsMap()
.get("HEAD")
.getTarget()
.getName();
}
List<GitBranchDTO> branchList = new ArrayList<>();
GitBranchDTO gitBranchDTO = new GitBranchDTO();
if (refList.isEmpty()) {
gitBranchDTO.setBranchName(git.getRepository().getBranch());
gitBranchDTO.setDefault(true);
branchList.add(gitBranchDTO);
} else {
if (Boolean.TRUE.equals(refreshBranches)) {
gitBranchDTO.setBranchName(defaultBranch.replace("refs/heads/", ""));
gitBranchDTO.setDefault(true);
branchList.add(gitBranchDTO);
}
for (Ref ref : refList) {
if (!ref.getName().equals(defaultBranch)) {
gitBranchDTO = new GitBranchDTO();
gitBranchDTO.setBranchName(ref.getName()
.replace("refs/", "")
.replace("heads/", "")
.replace("remotes/", ""));
gitBranchDTO.setDefault(false);
branchList.add(gitBranchDTO);
}
// if (!ref.getName().equals(defaultBranch)) {
gitBranchDTO = new GitBranchDTO();
gitBranchDTO.setBranchName(ref.getName()
.replace("refs/", "")
.replace("heads/", "")
.replace("remotes/", ""));
branchList.add(gitBranchDTO);
}
}
git.close();
processStopwatch.stopAndLogTimeInMillis();
return branchList;
})
.timeout(Duration.ofMillis(Constraint.TIMEOUT_MILLIS))
.subscribeOn(scheduler);
}
@Override
public Mono<String> getRemoteDefaultBranch(Path repoSuffix, String remoteUrl, String privateKey, String publicKey) {
Path baseRepoPath = createRepoPath(repoSuffix);
return Mono.fromCallable(() -> {
TransportConfigCallback transportConfigCallback =
new SshTransportConfigCallback(privateKey, publicKey);
Git git = Git.open(baseRepoPath.toFile());
return git.lsRemote()
.setRemote(remoteUrl)
.setTransportConfigCallback(transportConfigCallback)
.callAsMap()
.get("HEAD")
.getTarget()
.getName()
.replace("refs/heads/", "");
})
.timeout(Duration.ofMillis(Constraint.TIMEOUT_MILLIS))
.subscribeOn(scheduler);
}
/**
* This method will handle the git-status functionality
*

View File

@ -114,9 +114,13 @@ public interface GitExecutor {
* @param repoSuffix suffixedPath used to generate the base repo path this includes orgId, defaultAppId, repoName
* @return List of branches for the application
*/
Mono<List<GitBranchDTO>> listBranches(
Path repoSuffix, String remoteUrl, String privateKey, String publicKey, Boolean isDefaultBranchNeeded);
// Mono<List<GitBranchDTO>> listBranches(
// Path repoSuffix, String remoteUrl, String privateKey, String publicKey, Boolean
// isDefaultBranchNeeded);
Mono<String> getRemoteDefaultBranch(Path repoSuffix, String remoteUrl, String privateKey, String publicKey);
Mono<List<GitBranchDTO>> listBranches(Path repoSuffix);
/**
* This method will handle the git-status functionality
*

View File

@ -1,5 +1,6 @@
package com.appsmith.server.helpers;
import com.appsmith.server.domains.GitApplicationMetadata;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.util.WebClientUtils;
@ -95,4 +96,10 @@ public class GitUtils {
}
return sshUrl.split("\\.")[0].replaceFirst("git@", "");
}
public static String getDefaultBranchName(GitApplicationMetadata gitApplicationMetadata) {
return StringUtils.isEmptyOrNull(gitApplicationMetadata.getDefaultBranchName())
? gitApplicationMetadata.getBranchName()
: gitApplicationMetadata.getDefaultBranchName();
}
}

View File

@ -17,6 +17,7 @@ import com.appsmith.server.dtos.GitPullDTO;
import org.eclipse.jgit.lib.BranchTrackingStatus;
import reactor.core.publisher.Mono;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
@ -55,6 +56,8 @@ public interface GitServiceCE {
Mono<List<GitBranchDTO>> listBranchForApplication(
String defaultApplicationId, Boolean pruneBranches, String currentBranch);
Mono<String> syncDefaultBranchNameFromRemote(Path repoPath, Application rootApp);
Mono<GitApplicationMetadata> getGitApplicationMetadata(String defaultApplicationId);
Mono<GitStatusDTO> getStatus(String defaultApplicationId, boolean compareRemote, String branchName);

View File

@ -1163,7 +1163,7 @@ public class GitServiceCEImpl implements GitServiceCE {
String privateKey = gitApplicationMetadata.getGitAuth().getPrivateKey();
String publicKey = gitApplicationMetadata.getGitAuth().getPublicKey();
return Mono.zip(
gitExecutor.listBranches(repoSuffix, remoteUrl, privateKey, publicKey, false),
gitExecutor.listBranches(repoSuffix),
Mono.just(defaultApplication),
Mono.just(repoSuffix),
Mono.just(defaultApplicationBranchName));
@ -1305,12 +1305,7 @@ public class GitServiceCEImpl implements GitServiceCE {
.onErrorResume(error -> Mono.error(
new AppsmithException(AppsmithError.GIT_ACTION_FAILED, "fetch", error))))
.flatMap(ignore -> gitExecutor
.listBranches(
repoSuffix,
srcBranchGitData.getRemoteUrl(),
defaultGitAuth.getPrivateKey(),
defaultGitAuth.getPublicKey(),
false)
.listBranches(repoSuffix)
.flatMap(branchList -> {
boolean isDuplicateName = branchList.stream()
// We are only supporting origin as the remote name so this is safe
@ -1390,10 +1385,23 @@ public class GitServiceCEImpl implements GitServiceCE {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.BRANCH_NAME));
}
// get the root application
Mono<Application> rootAppMono = getApplicationById(defaultApplicationId);
// If the user is trying to check out remote branch, create a new branch if the branch does not exist already
if (branchName.startsWith("origin/")) {
String finalBranchName = branchName.replaceFirst("origin/", "");
return listBranchForApplication(defaultApplicationId, false, branchName)
return addFileLock(defaultApplicationId)
.then(rootAppMono)
.flatMap(application -> {
GitApplicationMetadata gitApplicationMetadata = application.getGitApplicationMetadata();
Path repoPath = Paths.get(
application.getWorkspaceId(),
gitApplicationMetadata.getDefaultApplicationId(),
gitApplicationMetadata.getRepoName());
return gitExecutor.listBranches(repoPath);
})
.flatMap(branchList -> releaseFileLock(defaultApplicationId).thenReturn(branchList))
.flatMap(gitBranchDTOList -> {
long branchMatchCount = gitBranchDTOList.stream()
.filter(gitBranchDTO ->
@ -1411,7 +1419,7 @@ public class GitServiceCEImpl implements GitServiceCE {
});
}
return getApplicationById(defaultApplicationId)
return rootAppMono
.flatMap(application -> {
if (isInvalidDefaultApplicationGitMetadata(application.getGitApplicationMetadata())) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_GIT_SSH_CONFIGURATION));
@ -1604,30 +1612,81 @@ public class GitServiceCEImpl implements GitServiceCE {
return Mono.create(sink -> pullMono.subscribe(sink::success, sink::error, null, sink.currentContext()));
}
private Flux<Application> updateDefaultBranchName(
Path repoPath, String defaultBranchName, String defaultApplicationId) {
// Get the application from DB by new defaultBranch name
return applicationService
.findByBranchNameAndDefaultApplicationId(
defaultBranchName, defaultApplicationId, applicationPermission.getEditPermission())
// Check if the branch is already present, If not follow checkout remote flow
.onErrorResume(throwable -> checkoutRemoteBranch(defaultApplicationId, defaultBranchName))
// ensure the local branch exists
.then(gitExecutor
.createAndCheckoutToBranch(repoPath, defaultBranchName)
.onErrorComplete())
// Update the default branch name in all the child applications
.thenMany(applicationService
.findAllApplicationsByDefaultApplicationId(
defaultApplicationId, applicationPermission.getEditPermission())
.flatMap(application2 -> {
application2.getGitApplicationMetadata().setDefaultBranchName(defaultBranchName);
return applicationService.save(application2);
}));
}
@Override
public Mono<String> syncDefaultBranchNameFromRemote(Path repoPath, Application rootApp) {
GitApplicationMetadata metadata = rootApp.getGitApplicationMetadata();
GitAuth gitAuth = metadata.getGitAuth();
return addFileLock(metadata.getDefaultApplicationId())
.then(gitExecutor.getRemoteDefaultBranch(
repoPath, metadata.getRemoteUrl(), gitAuth.getPrivateKey(), gitAuth.getPublicKey()))
.flatMap(defaultBranchNameInRemote -> {
String defaultBranchInDb = GitUtils.getDefaultBranchName(metadata);
if (StringUtils.isEmptyOrNull(defaultBranchNameInRemote)) {
// If the default branch name in remote is empty or same as the one in DB, nothing to do
return Mono.just(defaultBranchInDb);
}
return updateDefaultBranchName(
repoPath, defaultBranchNameInRemote, metadata.getDefaultApplicationId())
.then()
.thenReturn(defaultBranchNameInRemote);
})
.flatMap(branchName ->
releaseFileLock(metadata.getDefaultApplicationId()).thenReturn(branchName));
}
@Override
public Mono<List<GitBranchDTO>> listBranchForApplication(
String defaultApplicationId, Boolean pruneBranches, String currentBranch) {
// File lock
Mono<List<GitBranchDTO>> branchMono = getApplicationById(defaultApplicationId)
.flatMap(application -> addFileLock(defaultApplicationId).map(status -> application))
.flatMap(application -> {
// get the root application
Mono<Application> rootAppMono = getApplicationById(defaultApplicationId).cache();
Mono<Path> repoPathMono = rootAppMono
.map(application -> {
GitApplicationMetadata gitApplicationMetadata = application.getGitApplicationMetadata();
if (gitApplicationMetadata == null
|| gitApplicationMetadata.getDefaultApplicationId() == null
|| gitApplicationMetadata.getRepoName() == null) {
log.error("Git config is not present for application {}", defaultApplicationId);
return Mono.error(
new AppsmithException(AppsmithError.INVALID_GIT_CONFIGURATION, GIT_CONFIG_ERROR));
throw new AppsmithException(AppsmithError.INVALID_GIT_CONFIGURATION, GIT_CONFIG_ERROR);
}
Path repoPath = Paths.get(
return Paths.get(
application.getWorkspaceId(),
gitApplicationMetadata.getDefaultApplicationId(),
gitApplicationMetadata.getRepoName());
})
.cache();
Mono<List<GitBranchDTO>> gitBranchDTOMono;
// Fetch remote first if the prune branch is valid
Mono<List<GitBranchDTO>> gitBranchListMono = Mono.zip(rootAppMono, repoPathMono)
.flatMap(objects -> addFileLock(defaultApplicationId).thenReturn(objects))
.flatMap(objects -> {
GitApplicationMetadata gitApplicationMetadata =
objects.getT1().getGitApplicationMetadata();
Path repoPath = objects.getT2();
if (Boolean.TRUE.equals(pruneBranches)) {
gitBranchDTOMono = gitExecutor
return gitExecutor
.fetchRemote(
repoPath,
gitApplicationMetadata.getGitAuth().getPublicKey(),
@ -1635,82 +1694,49 @@ public class GitServiceCEImpl implements GitServiceCE {
false,
currentBranch,
true)
.flatMap(s -> gitExecutor.listBranches(
repoPath,
gitApplicationMetadata.getRemoteUrl(),
gitApplicationMetadata.getGitAuth().getPrivateKey(),
gitApplicationMetadata.getGitAuth().getPublicKey(),
true));
.then(gitExecutor.listBranches(repoPath));
} else {
// Fetch default branch from DB if the pruneBranches is false else fetch from remote
gitBranchDTOMono = gitExecutor.listBranches(
repoPath,
gitApplicationMetadata.getRemoteUrl(),
gitApplicationMetadata.getGitAuth().getPrivateKey(),
gitApplicationMetadata.getGitAuth().getPublicKey(),
false);
}
return Mono.zip(gitBranchDTOMono, Mono.just(application)).onErrorResume(error -> {
if (error instanceof RepositoryNotFoundException) {
Mono<List<GitBranchDTO>> branchListMono = handleRepoNotFoundException(defaultApplicationId);
return Mono.zip(branchListMono, Mono.just(application), Mono.just(repoPath));
}
return Mono.error(new AppsmithException(
AppsmithError.GIT_ACTION_FAILED, "branch --list", error.getMessage()));
});
})
.flatMap(objects -> releaseFileLock(defaultApplicationId).thenReturn(objects))
.flatMap(tuple -> {
List<GitBranchDTO> gitBranchListDTOS = tuple.getT1();
Application application = tuple.getT2();
GitApplicationMetadata gitApplicationMetadata = application.getGitApplicationMetadata();
final String dbDefaultBranch =
StringUtils.isEmptyOrNull(gitApplicationMetadata.getDefaultBranchName())
? gitApplicationMetadata.getBranchName()
: gitApplicationMetadata.getDefaultBranchName();
if (Boolean.TRUE.equals(pruneBranches)) {
String defaultBranchRemote = gitBranchListDTOS.stream()
.filter(GitBranchDTO::isDefault)
.map(GitBranchDTO::getBranchName)
.findFirst()
.orElse(dbDefaultBranch);
if (defaultBranchRemote.equals(dbDefaultBranch)) {
return Mono.just(gitBranchListDTOS).zipWith(Mono.just(application));
} else {
// Get the application from DB by new defaultBranch name
return applicationService
.findByBranchNameAndDefaultApplicationId(
defaultBranchRemote,
defaultApplicationId,
applicationPermission.getEditPermission())
// Check if the branch is already present, If not follow checkout remote flow
.onErrorResume(throwable ->
checkoutRemoteBranch(defaultApplicationId, defaultBranchRemote))
// Update the default branch name in all the child applications
.flatMapMany(application1 -> applicationService
.findAllApplicationsByDefaultApplicationId(
defaultApplicationId, applicationPermission.getEditPermission())
.flatMap(application2 -> {
application2
.getGitApplicationMetadata()
.setDefaultBranchName(defaultBranchRemote);
return applicationService.save(application2);
}))
// Return the deleted branches
.then(Mono.just(gitBranchListDTOS).zipWith(Mono.just(application)));
}
} else {
gitBranchListDTOS.stream()
.filter(branchDTO ->
StringUtils.equalsIgnoreCase(branchDTO.getBranchName(), dbDefaultBranch))
.findFirst()
.ifPresent(branchDTO -> branchDTO.setDefault(true));
return Mono.just(gitBranchListDTOS).zipWith(Mono.just(application));
return gitExecutor.listBranches(repoPath);
}
})
// Add BE analytics
.flatMap(branchDTOList -> releaseFileLock(defaultApplicationId).thenReturn(branchDTOList));
Mono<String> defaultBranchMono;
if (!Boolean.TRUE.equals(pruneBranches)) {
defaultBranchMono = rootAppMono
.map(application -> {
GitApplicationMetadata gitApplicationMetadata = application.getGitApplicationMetadata();
if (gitApplicationMetadata == null
|| gitApplicationMetadata.getDefaultApplicationId() == null) {
throw new AppsmithException(AppsmithError.INVALID_GIT_CONFIGURATION, GIT_CONFIG_ERROR);
}
return gitApplicationMetadata;
})
.map(GitUtils::getDefaultBranchName);
} else {
defaultBranchMono = Mono.zip(rootAppMono, repoPathMono).flatMap(objects -> {
Path repoPath = objects.getT2();
Application rootApp = objects.getT1();
return syncDefaultBranchNameFromRemote(repoPath, rootApp);
});
}
// set the default branch flag to true for the default branch
Mono<List<GitBranchDTO>> branchMono = defaultBranchMono
.zipWhen(defaultBranch -> gitBranchListMono)
.map(objects -> {
String defaultBranch = objects.getT1();
List<GitBranchDTO> gitBranchDTOList = objects.getT2();
for (GitBranchDTO branchDTO : gitBranchDTOList) {
if (StringUtils.equalsIgnoreCase(branchDTO.getBranchName(), defaultBranch)) {
branchDTO.setDefault(true);
break;
}
}
return gitBranchDTOList;
})
.zipWith(rootAppMono)
.flatMap(tuple -> {
List<GitBranchDTO> gitBranchDTOList = tuple.getT1();
Application application = tuple.getT2();
@ -2919,12 +2945,7 @@ public class GitServiceCEImpl implements GitServiceCE {
gitApplicationMetadata.getRemoteUrl(),
gitAuth.getPrivateKey(),
gitAuth.getPublicKey())
.flatMap(defaultBranch -> gitExecutor.listBranches(
repoPath,
gitApplicationMetadata.getRemoteUrl(),
gitAuth.getPrivateKey(),
gitAuth.getPublicKey(),
false))
.flatMap(defaultBranch -> gitExecutor.listBranches(repoPath))
.flatMap(gitBranchDTOList -> {
List<String> branchList = gitBranchDTOList.stream()
.filter(gitBranchDTO ->

View File

@ -263,8 +263,7 @@ public class GitExecutorTest {
Mono<String> branchMono = gitExecutor
.createAndCheckoutToBranch(path, "test1")
.flatMap(s -> gitExecutor.createAndCheckoutToBranch(path, "test2"));
Mono<List<GitBranchDTO>> gitBranchDTOMono =
branchMono.then(gitExecutor.listBranches(path, "remoteUrl", "publicKey", "privateKey", false));
Mono<List<GitBranchDTO>> gitBranchDTOMono = branchMono.then(gitExecutor.listBranches(path));
StepVerifier.create(gitBranchDTOMono).assertNext(gitBranchDTOS -> {
assertThat(gitBranchDTOS.stream().count()).isEqualTo(3);