use of org.finos.legend.sdlc.server.error.LegendSDLCServerException in project legend-sdlc by finos.
the class GitLabWorkspaceApi method attemptToSquashAndRebaseWorkspace.
/**
* This method is called when we failed to rebase the workspace branch on top of master branch. This implies that either
* the whole change is causing a merge conflict or one of the intermediate commits have conflicts with the change in master branch
* <p>
* To handle the latter case, this method will squash all commits on the workspace branch and attempt rebase again.
* If this fails, it implies that rebase fails because of merge conflicts, which means we have to enter conflict resolution route.
* <p>
* NOTE: based on the nature of this method, we have an optimization here where we check for the number of commits on the current
* branch, if it is less than 2 (not counting the base), it means no squashing is needed and we can just immediately tell that there
* is merge conflict and the workspace update should enter conflict resolution route
* <p>
* So following is the summary of this method:
* 1. Create a temp branch to do the squashing on that branch
* 3. Attempt to rebase the temp branch on master:
* - If succeeded: re-create current workspace branch on top of the temp branch
* - If failed -> implies conflict resolution is needed
*
* @return a workspace update report that might have status as UPDATED or CONFLICT.
*/
private WorkspaceUpdateReport attemptToSquashAndRebaseWorkspace(String projectId, String workspaceId, WorkspaceType workspaceType, String masterRevisionId, String currentWorkspaceRevisionId, String workspaceCreationRevisionId) {
GitLabProjectId gitLabProjectId = parseProjectId(projectId);
RepositoryApi repositoryApi = getGitLabApi(gitLabProjectId.getGitLabMode()).getRepositoryApi();
// Create temp branch for rebasing
String tempBranchName = newUserTemporaryBranchName();
Branch tempBranch;
try {
tempBranch = GitLabApiTools.createBranchAndVerify(repositoryApi, gitLabProjectId.getGitLabId(), tempBranchName, workspaceCreationRevisionId, 30, 1_000);
} catch (Exception e) {
throw buildException(e, () -> "User " + getCurrentUser() + " is not allowed to create temporary workspace " + tempBranchName + " in project " + projectId, () -> "Unknown project: " + projectId, () -> "Error creating temporary workspace " + tempBranchName + " in project " + projectId);
}
if (tempBranch == null) {
throw new LegendSDLCServerException("Failed to create temporary workspace " + tempBranchName + " in project " + projectId + " from revision " + workspaceCreationRevisionId);
}
CompareResults comparisonResult;
try {
comparisonResult = withRetries(() -> repositoryApi.compare(gitLabProjectId.getGitLabId(), workspaceCreationRevisionId, currentWorkspaceRevisionId, true));
} catch (Exception e) {
throw buildException(e, () -> "User " + getCurrentUser() + " is not allowed to get comparison information from revision " + workspaceCreationRevisionId + " to revision " + currentWorkspaceRevisionId + " on project" + projectId, () -> "Could not find revisions " + workspaceCreationRevisionId + " ," + currentWorkspaceRevisionId + " on project" + projectId, () -> "Failed to fetch comparison information from revision " + workspaceCreationRevisionId + " to revision " + currentWorkspaceRevisionId + " on project" + projectId);
}
// Create a new commit on temp branch that squashes all changes on the concerned workspace branch
CommitsApi commitsApi = getGitLabApi(gitLabProjectId.getGitLabMode()).getCommitsApi();
ProjectFileAccessProvider.WorkspaceAccessType workspaceAccessType = ProjectFileAccessProvider.WorkspaceAccessType.WORKSPACE;
ProjectFileAccessProvider.FileAccessContext workspaceFileAccessContext = getProjectFileAccessProvider().getWorkspaceFileAccessContext(projectId, workspaceId, workspaceType, workspaceAccessType);
Commit squashedCommit;
try {
List<CommitAction> commitActions = Lists.mutable.empty();
comparisonResult.getDiffs().forEach(diff -> {
if (diff.getDeletedFile()) {
// DELETE
commitActions.add(new CommitAction().withAction(CommitAction.Action.DELETE).withFilePath(diff.getOldPath()));
} else if (diff.getRenamedFile()) {
// MOVE
//
// tl;dr;
// split this into a delete and a create to make sure the moved entity has the content of the entity
// in workspace HEAD revision
//
// Since we use comparison API to compute the diff, Git has a smart algorithm to calculate file move
// as it is a heuristics such that if file content is only slightly different, Git can conclude that the
// diff was of type RENAME.
//
// The problem with this is when we compare the workspace BASE revision and workspace HEAD revision
// Assume that we actually renamed an entity, we also want to update the entity path, not just its location
// the location part is correctly identified by Git, and the change in content is captured as a diff string
// in comparison result (which shows the change in the path)
// but when we create CommitAction, there is no way for us to apply this patch on top of the entity content
// so if we just create action of type MOVE for CommitAction, we will have the content of the old entity
// which has the wrong path, and thus this entity if continue to exist in the workspace will throw off
// our path and entity location validation check
commitActions.add(new CommitAction().withAction(CommitAction.Action.DELETE).withFilePath(diff.getOldPath()));
commitActions.add(new CommitAction().withAction(CommitAction.Action.CREATE).withFilePath(diff.getNewPath()).withEncoding(Constants.Encoding.BASE64).withContent(encodeBase64(workspaceFileAccessContext.getFile(diff.getNewPath()).getContentAsBytes())));
} else if (diff.getNewFile()) {
// CREATE
commitActions.add(new CommitAction().withAction(CommitAction.Action.CREATE).withFilePath(diff.getNewPath()).withEncoding(Constants.Encoding.BASE64).withContent(encodeBase64(workspaceFileAccessContext.getFile(diff.getNewPath()).getContentAsBytes())));
} else {
// UPDATE
commitActions.add(new CommitAction().withAction(CommitAction.Action.UPDATE).withFilePath(diff.getOldPath()).withEncoding(Constants.Encoding.BASE64).withContent(encodeBase64(workspaceFileAccessContext.getFile(diff.getOldPath()).getContentAsBytes())));
}
});
squashedCommit = commitsApi.createCommit(gitLabProjectId.getGitLabId(), tempBranchName, "aggregated changes for workspace " + workspaceId, null, null, getCurrentUser(), commitActions);
} catch (Exception e) {
throw buildException(e, () -> "User " + getCurrentUser() + " is not allowed to create commit on temporary workspace " + tempBranchName + " of project " + projectId, () -> "Unknown project: " + projectId + " or temporary workspace " + tempBranchName, () -> "Failed to create commit in temporary workspace " + tempBranchName + " of project " + projectId);
}
// Attempt to rebase the temporary branch on top of master
boolean attemptRebaseResult = this.attemptToRebaseWorkspaceUsingTemporaryBranch(projectId, workspaceId, workspaceType, tempBranchName, masterRevisionId);
// If rebasing failed, this implies there are conflicts, otherwise, the workspace should be updated
if (!attemptRebaseResult) {
return createWorkspaceUpdateReport(WorkspaceUpdateReportStatus.CONFLICT, null, null);
}
return createWorkspaceUpdateReport(WorkspaceUpdateReportStatus.UPDATED, masterRevisionId, squashedCommit.getId());
}
use of org.finos.legend.sdlc.server.error.LegendSDLCServerException in project legend-sdlc by finos.
the class GitlabWorkflowApi method getReviewWorkflowAccessContext.
@Override
public WorkflowAccessContext getReviewWorkflowAccessContext(String projectId, String reviewId) {
LegendSDLCServerException.validateNonNull(projectId, "projectId may not be null");
LegendSDLCServerException.validateNonNull(reviewId, "reviewId may not be null");
GitLabProjectId gitLabProjectId = parseProjectId(projectId);
MergeRequestApi mergeRequestApi = getGitLabApi(gitLabProjectId.getGitLabMode()).getMergeRequestApi();
MergeRequest mergeRequest = getReviewMergeRequest(mergeRequestApi, gitLabProjectId, reviewId, true);
WorkspaceInfo workspaceInfo = parseWorkspaceBranchName(mergeRequest.getSourceBranch());
if (workspaceInfo == null) {
throw new LegendSDLCServerException("Unknown review in project " + projectId + ": " + reviewId, Response.Status.NOT_FOUND);
}
return new GitLabWorkflowAccessContext(gitLabProjectId) {
@Override
protected String getRef() {
return "refs/merge-requests/" + reviewId + "/head";
}
@Override
protected String getRefInfoForException() {
return "review " + reviewId + " of project " + projectId;
}
@Override
protected ProjectFileAccessProvider.RevisionAccessContext getRevisionAccessContext() {
return getProjectFileAccessProvider().getWorkspaceRevisionAccessContext(projectId, workspaceInfo.getWorkspaceId(), workspaceInfo.getWorkspaceType(), workspaceInfo.getWorkspaceAccessType());
}
};
}
use of org.finos.legend.sdlc.server.error.LegendSDLCServerException in project legend-sdlc by finos.
the class GitLabBackupApi method discardBackupWorkspace.
@Override
public void discardBackupWorkspace(String projectId, String workspaceId, WorkspaceType workspaceType) {
LegendSDLCServerException.validateNonNull(projectId, "projectId may not be null");
LegendSDLCServerException.validateNonNull(workspaceId, "workspaceId may not be null");
GitLabProjectId gitLabProjectId = parseProjectId(projectId);
RepositoryApi repositoryApi = getGitLabApi(gitLabProjectId.getGitLabMode()).getRepositoryApi();
boolean backupWorkspaceDeleted;
ProjectFileAccessProvider.WorkspaceAccessType backupWorkspaceType = ProjectFileAccessProvider.WorkspaceAccessType.BACKUP;
try {
backupWorkspaceDeleted = GitLabApiTools.deleteBranchAndVerify(repositoryApi, gitLabProjectId.getGitLabId(), getWorkspaceBranchName(workspaceId, workspaceType, backupWorkspaceType), 20, 1_000);
} catch (Exception e) {
throw buildException(e, () -> "User " + getCurrentUser() + " is not allowed to delete " + workspaceType.getLabel() + " " + backupWorkspaceType.getLabel() + " " + workspaceId + " in project " + projectId, () -> "Unknown " + workspaceType.getLabel() + " " + backupWorkspaceType.getLabel() + " (" + workspaceId + ") or project (" + projectId + ")", () -> "Error deleting " + workspaceType.getLabel() + " " + backupWorkspaceType.getLabel() + " " + workspaceId + " in project " + projectId);
}
if (!backupWorkspaceDeleted) {
throw new LegendSDLCServerException("Failed to delete " + workspaceType.getLabel() + " " + backupWorkspaceType.getLabel() + " " + workspaceId + " in project " + projectId);
}
}
use of org.finos.legend.sdlc.server.error.LegendSDLCServerException in project legend-sdlc by finos.
the class GitLabBackupApi method recoverBackupWorkspace.
/**
* This method will recover a backup workspace by doing the following step:
* 1. Verify that the backup workspace exists
* 2. Delete the existing workspace
* 3. Create
*/
@Override
public void recoverBackupWorkspace(String projectId, String workspaceId, WorkspaceType workspaceType, boolean forceRecovery) {
LegendSDLCServerException.validateNonNull(projectId, "projectId may not be null");
LegendSDLCServerException.validateNonNull(workspaceId, "workspaceId may not be null");
GitLabProjectId gitLabProjectId = parseProjectId(projectId);
RepositoryApi repositoryApi = getGitLabApi(gitLabProjectId.getGitLabMode()).getRepositoryApi();
ProjectFileAccessProvider.WorkspaceAccessType backupWorkspaceType = ProjectFileAccessProvider.WorkspaceAccessType.BACKUP;
// Verify the backup exists
try {
withRetries(() -> repositoryApi.getBranch(gitLabProjectId.getGitLabId(), getWorkspaceBranchName(workspaceId, workspaceType, backupWorkspaceType)));
} catch (Exception e) {
if (GitLabApiTools.isNotFoundGitLabApiException(e)) {
LOGGER.error("No backup for workspace {} in project {}, so recovery is not possible", workspaceId, projectId);
}
throw buildException(e, () -> "User " + getCurrentUser() + " is not allowed to get " + workspaceType.getLabel() + " " + backupWorkspaceType.getLabel() + " " + workspaceId + " in project " + projectId, () -> "Unknown " + workspaceType.getLabel() + " " + backupWorkspaceType.getLabel() + " with (" + workspaceId + ") or project (" + projectId + "). " + "This implies that a backup does not exist for the specified workspace, hence recovery is not possible", () -> "Error getting " + workspaceType.getLabel() + " " + backupWorkspaceType.getLabel() + " " + workspaceId + " in project " + projectId);
}
Branch existingBranch = null;
ProjectFileAccessProvider.WorkspaceAccessType workspaceAccessType = ProjectFileAccessProvider.WorkspaceAccessType.WORKSPACE;
// Check if branch exists
try {
existingBranch = withRetries(() -> repositoryApi.getBranch(gitLabProjectId.getGitLabId(), getWorkspaceBranchName(workspaceId, workspaceType, workspaceAccessType)));
} catch (Exception e) {
if (!GitLabApiTools.isNotFoundGitLabApiException(e)) {
LOGGER.error("Error getting {} {} in project {}", workspaceType.getLabel() + " " + workspaceAccessType.getLabel(), workspaceId, projectId, e);
}
}
if (existingBranch != null) {
if (!forceRecovery) {
throw new LegendSDLCServerException("Workspace " + workspaceId + " of project " + projectId + " already existed and the recovery is not forced, so recovery from backup is not possible", Response.Status.METHOD_NOT_ALLOWED);
}
// Delete the existing branch
boolean workspaceDeleted;
try {
workspaceDeleted = GitLabApiTools.deleteBranchAndVerify(repositoryApi, gitLabProjectId.getGitLabId(), getWorkspaceBranchName(workspaceId, workspaceType, workspaceAccessType), 20, 1_000);
} catch (Exception e) {
throw buildException(e, () -> "Error while attempting to recover backup for " + workspaceType.getLabel() + " " + workspaceAccessType.getLabel() + " " + workspaceId + " in project " + projectId + ": User " + getCurrentUser() + " is not allowed to delete workspace", () -> "Error while attempting to recover backup for " + workspaceType.getLabel() + " " + workspaceAccessType.getLabel() + " " + workspaceId + " in project " + projectId + ": Unknown project: " + projectId, () -> "Error while attempting to recover backup for " + workspaceType.getLabel() + " " + workspaceAccessType.getLabel() + " " + workspaceId + " in project " + projectId + ": Error deleting workspace");
}
if (!workspaceDeleted) {
throw new LegendSDLCServerException("Failed to delete " + workspaceType.getLabel() + " " + workspaceAccessType.getLabel() + " " + workspaceId + " in project " + projectId);
}
}
// Create new workspace branch off the backup branch head
Branch workspaceBranch;
try {
workspaceBranch = GitLabApiTools.createBranchFromSourceBranchAndVerify(repositoryApi, gitLabProjectId.getGitLabId(), getWorkspaceBranchName(workspaceId, workspaceType, workspaceAccessType), getWorkspaceBranchName(workspaceId, workspaceType, backupWorkspaceType), 30, 1_000);
} catch (Exception e) {
throw buildException(e, () -> "User " + getCurrentUser() + " is not allowed to create " + workspaceType.getLabel() + " " + workspaceAccessType.getLabel() + " " + workspaceId + " in project " + projectId, () -> "Unknown project: " + projectId, () -> "Error creating " + workspaceType.getLabel() + " " + workspaceAccessType.getLabel() + " " + workspaceId + " in project " + projectId);
}
if (workspaceBranch == null) {
throw new LegendSDLCServerException("Failed to create " + workspaceType.getLabel() + " " + workspaceAccessType.getLabel() + " " + workspaceId + " in project " + projectId + " from " + workspaceType.getLabel() + " " + backupWorkspaceType.getLabel() + " " + workspaceId);
}
// Delete backup branch
try {
boolean deleted = GitLabApiTools.deleteBranchAndVerify(repositoryApi, gitLabProjectId.getGitLabId(), getWorkspaceBranchName(workspaceId, workspaceType, backupWorkspaceType), 20, 1_000);
if (!deleted) {
LOGGER.error("Failed to delete {} {} in project {}", workspaceType.getLabel() + " " + backupWorkspaceType.getLabel(), workspaceId, projectId);
}
} catch (Exception e) {
// unfortunate, but this should not throw error
LOGGER.error("Error deleting {} {} in project {} after recovery is completed", workspaceType.getLabel() + " " + backupWorkspaceType.getLabel(), workspaceId, projectId, e);
}
}
use of org.finos.legend.sdlc.server.error.LegendSDLCServerException in project legend-sdlc by finos.
the class GitLabComparisonApi method getReviewComparison.
@Override
public Comparison getReviewComparison(String projectId, String reviewId) {
LegendSDLCServerException.validateNonNull(projectId, "projectId may not be null");
LegendSDLCServerException.validateNonNull(reviewId, "reviewId may not be null");
GitLabProjectId gitLabProjectId = parseProjectId(projectId);
RepositoryApi repositoryApi = getGitLabApi(gitLabProjectId.getGitLabMode()).getRepositoryApi();
MergeRequest mergeRequest = getReviewMergeRequest(getGitLabApi(gitLabProjectId.getGitLabMode()).getMergeRequestApi(), gitLabProjectId, reviewId);
WorkspaceInfo workspaceInfo = parseWorkspaceBranchName(mergeRequest.getSourceBranch());
if (workspaceInfo == null) {
throw new LegendSDLCServerException("Unknown review in project " + projectId + ": " + reviewId, Response.Status.NOT_FOUND);
}
DiffRef diffRef = mergeRequest.getDiffRefs();
if ((diffRef == null) || (diffRef.getStartSha() == null) || (diffRef.getHeadSha() == null)) {
throw new LegendSDLCServerException("Unable to get revision info for review " + reviewId + " in project " + projectId);
}
String fromRevisionId = diffRef.getStartSha();
String toRevisionId = diffRef.getHeadSha();
ProjectStructure fromProjectStructure = getProjectStructure(projectId, workspaceInfo.getWorkspaceId(), fromRevisionId, workspaceInfo.getWorkspaceType(), workspaceInfo.getWorkspaceAccessType());
ProjectStructure toProjectStructure = getProjectStructure(gitLabProjectId.toString(), null, toRevisionId, null, null);
return getComparisonResult(gitLabProjectId, repositoryApi, fromRevisionId, toRevisionId, fromProjectStructure, toProjectStructure);
}
Aggregations