/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.plugin.pullrequest.client.vcs;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.che.api.core.model.project.ProjectConfig;
import org.eclipse.che.api.git.shared.Branch;
import org.eclipse.che.api.git.shared.BranchListMode;
import org.eclipse.che.api.git.shared.CheckoutRequest;
import org.eclipse.che.api.git.shared.PushResponse;
import org.eclipse.che.api.git.shared.Remote;
import org.eclipse.che.api.git.shared.Revision;
import org.eclipse.che.api.git.shared.Status;
import org.eclipse.che.api.promises.client.Function;
import org.eclipse.che.api.promises.client.FunctionException;
import org.eclipse.che.api.promises.client.Operation;
import org.eclipse.che.api.promises.client.OperationException;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.api.promises.client.PromiseError;
import org.eclipse.che.api.promises.client.js.JsPromiseError;
import org.eclipse.che.api.promises.client.js.Promises;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.git.GitServiceClient;
import org.eclipse.che.ide.dto.DtoFactory;
import org.eclipse.che.ide.resource.Path;
import org.eclipse.che.ide.rest.AsyncRequestCallback;
import org.eclipse.che.ide.rest.DtoUnmarshallerFactory;
import org.eclipse.che.ide.rest.Unmarshallable;
import org.eclipse.che.ide.websocket.WebSocketException;
import org.eclipse.che.ide.websocket.rest.RequestCallback;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Git backed implementation for {@link VcsService}.
*/
@Singleton
public class GitVcsService implements VcsService {
private static final String BRANCH_UP_TO_DATE_ERROR_MESSAGE = "Everything up-to-date";
private final GitServiceClient service;
private final DtoFactory dtoFactory;
private final DtoUnmarshallerFactory dtoUnmarshallerFactory;
private final AppContext appContext;
@Inject
public GitVcsService(final DtoFactory dtoFactory,
final DtoUnmarshallerFactory dtoUnmarshallerFactory,
final GitServiceClient service,
final AppContext appContext) {
this.dtoFactory = dtoFactory;
this.dtoUnmarshallerFactory = dtoUnmarshallerFactory;
this.service = service;
this.appContext = appContext;
}
@Override
public void addRemote(@NotNull final ProjectConfig project, @NotNull final String remote, @NotNull final String remoteUrl,
@NotNull final AsyncCallback<Void> callback) {
service.remoteAdd(appContext.getDevMachine(), project, remote, remoteUrl, new AsyncRequestCallback<String>() {
@Override
protected void onSuccess(final String notUsed) {
callback.onSuccess(null);
}
@Override
protected void onFailure(final Throwable exception) {
callback.onFailure(exception);
}
});
}
@Override
public void checkoutBranch(@NotNull final ProjectConfig project, @NotNull final String name,
final boolean createNew, @NotNull final AsyncCallback<String> callback) {
service.checkout(appContext.getDevMachine(),
project,
dtoFactory.createDto(CheckoutRequest.class)
.withName(name)
.withCreateNew(createNew),
new AsyncRequestCallback<String>() {
@Override
protected void onSuccess(final String branchName) {
callback.onSuccess(branchName);
}
@Override
protected void onFailure(final Throwable exception) {
callback.onFailure(exception);
}
});
}
@Override
public void commit(@NotNull final ProjectConfig project, final boolean includeUntracked, @NotNull final String commitMessage,
@NotNull final AsyncCallback<Void> callback) {
try {
service.add(appContext.getDevMachine(), project, !includeUntracked, null, new RequestCallback<Void>() {
@Override
protected void onSuccess(Void aVoid) {
service.commit(appContext.getDevMachine(), project, commitMessage, true, false, new AsyncRequestCallback<Revision>() {
@Override
protected void onSuccess(final Revision revision) {
callback.onSuccess(null);
}
@Override
protected void onFailure(final Throwable exception) {
callback.onFailure(exception);
}
});
}
@Override
protected void onFailure(final Throwable exception) {
callback.onFailure(exception);
}
});
} catch (final WebSocketException exception) {
callback.onFailure(exception);
}
}
@Override
public void deleteRemote(@NotNull final ProjectConfig project, @NotNull final String remote,
@NotNull final AsyncCallback<Void> callback) {
service.remoteDelete(appContext.getDevMachine(), project, remote, new AsyncRequestCallback<String>() {
@Override
protected void onSuccess(final String notUsed) {
callback.onSuccess(null);
}
@Override
protected void onFailure(final Throwable exception) {
callback.onFailure(exception);
}
});
}
@Override
public Promise<String> getBranchName(ProjectConfig project) {
return service.getStatus(appContext.getDevMachine(), Path.valueOf(project.getPath()))
.then(new Function<Status, String>() {
@Override
public String apply(Status status) throws FunctionException {
return status.getBranchName();
}
});
}
@Override
public void hasUncommittedChanges(@NotNull final ProjectConfig project, @NotNull final AsyncCallback<Boolean> callback) {
service.getStatus(appContext.getDevMachine(), Path.valueOf(project.getPath()))
.then(new Operation<Status>() {
@Override
public void apply(Status status) throws OperationException {
callback.onSuccess(!status.isClean());
}
})
.catchError(new Operation<PromiseError>() {
@Override
public void apply(PromiseError err) throws OperationException {
callback.onFailure(err.getCause());
}
});
}
@Override
public void isLocalBranchWithName(@NotNull final ProjectConfig project, @NotNull final String branchName,
@NotNull final AsyncCallback<Boolean> callback) {
listLocalBranches(project, new AsyncCallback<List<Branch>>() {
@Override
public void onFailure(final Throwable exception) {
callback.onFailure(exception);
}
@Override
public void onSuccess(final List<Branch> branches) {
for (final Branch oneBranch : branches) {
if (oneBranch.getDisplayName().equals(branchName)) {
callback.onSuccess(true);
return;
}
}
callback.onSuccess(false);
}
});
}
@Override
public void listLocalBranches(@NotNull final ProjectConfig project, @NotNull final AsyncCallback<List<Branch>> callback) {
listBranches(project, null, callback);
}
@Override
public Promise<List<Remote>> listRemotes(ProjectConfig project) {
return service.remoteList(appContext.getDevMachine(), project, null, false);
}
@Override
public Promise<PushResponse> pushBranch(final ProjectConfig project, final String remote, final String localBranchName) {
return service.push(appContext.getDevMachine(), project, Collections.singletonList(localBranchName), remote, true)
.catchErrorPromise(new Function<PromiseError, Promise<PushResponse>>() {
@Override
public Promise<PushResponse> apply(PromiseError error) throws FunctionException {
if (BRANCH_UP_TO_DATE_ERROR_MESSAGE.equalsIgnoreCase(error.getMessage())) {
return Promises.reject(JsPromiseError.create(new BranchUpToDateException(localBranchName)));
} else {
return Promises.reject(error);
}
}
});
}
/**
* List branches of a given type.
*
* @param project
* the project descriptor.
* @param listMode
* null -> list local branches; "r" -> list remote branches; "a" -> list all branches.
* @param callback
* callback when the operation is done.
*/
private void listBranches(final ProjectConfig project, final BranchListMode listMode, final AsyncCallback<List<Branch>> callback) {
final Unmarshallable<List<Branch>> unMarshaller =
dtoUnmarshallerFactory.newListUnmarshaller(Branch.class);
service.branchList(appContext.getDevMachine(), project, listMode,
new AsyncRequestCallback<List<Branch>>(unMarshaller) {
@Override
protected void onSuccess(final List<Branch> branches) {
final List<Branch> result = new ArrayList<>();
for (final Branch branch : branches) {
result.add(fromGitBranch(branch));
}
callback.onSuccess(result);
}
@Override
protected void onFailure(final Throwable exception) {
callback.onFailure(exception);
}
});
}
/**
* Converts a git branch DTO to an abstracted {@link org.eclipse.che.api.git.shared.Branch} object.
*
* @param gitBranch
* the object to convert.
* @return the converted object.
*/
private Branch fromGitBranch(final Branch gitBranch) {
final Branch branch = GitVcsService.this.dtoFactory.createDto(Branch.class);
branch.withActive(gitBranch.isActive()).withRemote(gitBranch.isRemote())
.withName(gitBranch.getName()).withDisplayName(gitBranch.getDisplayName());
return branch;
}
/**
* Converts a git remote DTO to an abstracted {@link org.eclipse.che.api.git.shared.Remote} object.
*
* @param gitRemote
* the object to convert.
* @return the converted object.
*/
private Remote fromGitRemote(final Remote gitRemote) {
final Remote remote = GitVcsService.this.dtoFactory.createDto(Remote.class);
remote.withName(gitRemote.getName()).withUrl(gitRemote.getUrl());
return remote;
}
}