/*******************************************************************************
* 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.api.git;
import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.git.exception.GitException;
import org.eclipse.che.api.git.params.AddParams;
import org.eclipse.che.api.git.params.CheckoutParams;
import org.eclipse.che.api.git.params.CloneParams;
import org.eclipse.che.api.git.params.CommitParams;
import org.eclipse.che.api.git.params.DiffParams;
import org.eclipse.che.api.git.params.FetchParams;
import org.eclipse.che.api.git.params.LogParams;
import org.eclipse.che.api.git.params.PullParams;
import org.eclipse.che.api.git.params.PushParams;
import org.eclipse.che.api.git.params.RemoteAddParams;
import org.eclipse.che.api.git.params.RemoteUpdateParams;
import org.eclipse.che.api.git.params.ResetParams;
import org.eclipse.che.api.git.params.RmParams;
import org.eclipse.che.api.git.params.TagCreateParams;
import org.eclipse.che.api.git.shared.AddRequest;
import org.eclipse.che.api.git.shared.Branch;
import org.eclipse.che.api.git.shared.BranchCreateRequest;
import org.eclipse.che.api.git.shared.BranchListMode;
import org.eclipse.che.api.git.shared.CheckoutRequest;
import org.eclipse.che.api.git.shared.CloneRequest;
import org.eclipse.che.api.git.shared.CommitRequest;
import org.eclipse.che.api.git.shared.Commiters;
import org.eclipse.che.api.git.shared.ConfigRequest;
import org.eclipse.che.api.git.shared.Constants;
import org.eclipse.che.api.git.shared.DiffType;
import org.eclipse.che.api.git.shared.FetchRequest;
import org.eclipse.che.api.git.shared.MergeRequest;
import org.eclipse.che.api.git.shared.MergeResult;
import org.eclipse.che.api.git.shared.MoveRequest;
import org.eclipse.che.api.git.shared.PullRequest;
import org.eclipse.che.api.git.shared.PullResponse;
import org.eclipse.che.api.git.shared.PushRequest;
import org.eclipse.che.api.git.shared.PushResponse;
import org.eclipse.che.api.git.shared.RebaseRequest;
import org.eclipse.che.api.git.shared.RebaseResponse;
import org.eclipse.che.api.git.shared.Remote;
import org.eclipse.che.api.git.shared.RemoteAddRequest;
import org.eclipse.che.api.git.shared.RemoteUpdateRequest;
import org.eclipse.che.api.git.shared.RepoInfo;
import org.eclipse.che.api.git.shared.ResetRequest;
import org.eclipse.che.api.git.shared.Revision;
import org.eclipse.che.api.git.shared.ShowFileContentResponse;
import org.eclipse.che.api.git.shared.Status;
import org.eclipse.che.api.git.shared.StatusFormat;
import org.eclipse.che.api.git.shared.Tag;
import org.eclipse.che.api.git.shared.TagCreateRequest;
import org.eclipse.che.api.project.server.FolderEntry;
import org.eclipse.che.api.project.server.ProjectRegistry;
import org.eclipse.che.api.project.server.RegisteredProject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
/**
* Defines Git REST API.
*
* @author andrew00x
* @author Igor Vinokur
*/
@Path("git")
public class GitService {
private static final Logger LOG = LoggerFactory.getLogger(GitService.class);
@Inject
private GitConnectionFactory gitConnectionFactory;
@Inject
private ProjectRegistry projectRegistry;
@QueryParam("projectPath")
private String projectPath;
@POST
@Path("add")
@Consumes(MediaType.APPLICATION_JSON)
public void add(AddRequest request) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
gitConnection.add(AddParams.create(request.getFilePattern())
.withUpdate(request.isUpdate()));
}
}
@POST
@Path("checkout")
@Consumes(MediaType.APPLICATION_JSON)
public void checkout(CheckoutRequest request) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
gitConnection.checkout(CheckoutParams.create(request.getName())
.withFiles(request.getFiles())
.withCreateNew(request.isCreateNew())
.withNoTrack(request.isNoTrack())
.withTrackBranch(request.getTrackBranch())
.withStartPoint(request.getStartPoint()));
}
}
@POST
@Path("branch")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Branch branchCreate(BranchCreateRequest request) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
return gitConnection.branchCreate(request.getName(), request.getStartPoint());
}
}
@DELETE
@Path("branch")
public void branchDelete(@QueryParam("name") String name, @QueryParam("force") boolean force) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
gitConnection.branchDelete(name, force);
}
}
@POST
@Path("branch")
public void branchRename(@QueryParam("oldName") String oldName,
@QueryParam("newName") String newName) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
gitConnection.branchRename(oldName, newName);
}
}
@GET
@Path("branch")
@Produces(MediaType.APPLICATION_JSON)
public List<Branch> branchList(@QueryParam("listMode") String listMode) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
return gitConnection.branchList(listMode == null ? null : BranchListMode.valueOf(listMode));
} catch (IllegalArgumentException exception) {
throw new BadRequestException(exception.getMessage());
}
}
@POST
@Path("clone")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public RepoInfo clone(final CloneRequest request) throws URISyntaxException, ApiException {
long start = System.currentTimeMillis();
LOG.info("Repository clone from '" + request.getRemoteUri() + "' to '" + request.getWorkingDir() + "' started");
GitConnection gitConnection = getGitConnection();
try {
gitConnection.clone(CloneParams.create(request.getRemoteUri())
// On-the-fly resolving of repository's working directory.
.withWorkingDir(getAbsoluteProjectPath(request.getWorkingDir()))
.withBranchesToFetch(request.getBranchesToFetch())
.withRemoteName(request.getRemoteName())
.withTimeout(request.getTimeout())
.withUsername(request.getUsername())
.withPassword(request.getPassword()));
return newDto(RepoInfo.class).withRemoteUri(request.getRemoteUri());
} finally {
long end = System.currentTimeMillis();
long seconds = (end - start) / 1000;
LOG.info("Repository clone from '" + request.getRemoteUri() + "' to '" + request.getWorkingDir()
+ "' finished. Process took " + seconds + " seconds (" + seconds / 60 + " minutes)");
gitConnection.close();
}
}
@POST
@Path("commit")
@Consumes(MediaType.APPLICATION_JSON)
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
public Revision commit(CommitRequest request) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
return gitConnection.commit(CommitParams.create(request.getMessage())
.withFiles(request.getFiles())
.withAll(request.isAll())
.withAmend(request.isAmend()));
}
}
@GET
@Path("diff")
@Produces(MediaType.TEXT_PLAIN)
public InfoPage diff(@QueryParam("fileFilter") List<String> fileFilter,
@QueryParam("diffType") String diffType,
@QueryParam("noRenames") boolean noRenames,
@QueryParam("renameLimit") int renameLimit,
@QueryParam("commitA") String commitA,
@QueryParam("commitB") String commitB,
@QueryParam("cached") boolean cached) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
return gitConnection.diff(DiffParams.create()
.withFileFilter(fileFilter)
.withType(diffType == null ? null : DiffType.valueOf(diffType))
.withNoRenames(noRenames)
.withRenameLimit(renameLimit)
.withCommitA(commitA)
.withCommitB(commitB)
.withCached(cached));
}
}
@GET
@Path("show")
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
public ShowFileContentResponse showFileContent(@QueryParam("file") String file, @QueryParam("version") String version)
throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
return gitConnection.showFileContent(file, version);
}
}
@POST
@Path("fetch")
@Consumes(MediaType.APPLICATION_JSON)
public void fetch(FetchRequest request) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
gitConnection.fetch(FetchParams.create(request.getRemote())
.withRefSpec(request.getRefSpec())
.withTimeout(request.getTimeout())
.withRemoveDeletedRefs(request.isRemoveDeletedRefs())
.withUsername(request.getUsername())
.withPassword(request.getPassword()));
}
}
@POST
@Path("init")
public void init(@QueryParam("bare") boolean bare) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
gitConnection.init(bare);
}
projectRegistry.setProjectType(projectPath, GitProjectType.TYPE_ID, true);
}
@DELETE
@Path("repository")
public void deleteRepository(@Context UriInfo uriInfo) throws ApiException {
final RegisteredProject project = projectRegistry.getProject(projectPath);
final FolderEntry gitFolder = project.getBaseFolder().getChildFolder(".git");
gitFolder.getVirtualFile().delete();
projectRegistry.removeProjectType(projectPath, GitProjectType.TYPE_ID);
}
@GET
@Path("log")
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
public LogPage log(@QueryParam("fileFilter") List<String> fileFilter,
@QueryParam("since") String revisionRangeSince,
@QueryParam("until") String revisionRangeUntil,
@QueryParam("skip") @DefaultValue("0") int skip,
@QueryParam("maxCount") @DefaultValue(Constants.DEFAULT_PAGE_SIZE_QUERY_PARAM) int maxCount) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
return gitConnection.log(LogParams.create()
.withFileFilter(fileFilter)
.withRevisionRangeSince(revisionRangeSince)
.withRevisionRangeUntil(revisionRangeUntil)
.withMaxCount(maxCount)
.withSkip(skip));
}
}
@POST
@Path("merge")
@Consumes(MediaType.APPLICATION_JSON)
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
public MergeResult merge(MergeRequest request) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
return gitConnection.merge(request.getCommit());
}
}
@POST
@Path("rebase")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public RebaseResponse rebase(RebaseRequest request) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
return gitConnection.rebase(request.getOperation(), request.getBranch());
}
}
@POST
@Path("move")
@Consumes(MediaType.APPLICATION_JSON)
public void move(MoveRequest request) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
gitConnection.mv(request.getSource(), request.getTarget());
}
}
@DELETE
@Path("remove")
public void remove(@QueryParam("items") List<String> items,
@QueryParam("cached") boolean cached) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
gitConnection.rm(RmParams.create(items).withCached(cached));
}
}
@POST
@Path("pull")
@Consumes(MediaType.APPLICATION_JSON)
public PullResponse pull(PullRequest request) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
return gitConnection.pull(PullParams.create(request.getRemote())
.withRefSpec(request.getRefSpec())
.withTimeout(request.getTimeout())
.withUsername(request.getUsername())
.withPassword(request.getPassword()));
}
}
@POST
@Path("push")
@Consumes(MediaType.APPLICATION_JSON)
public PushResponse push(PushRequest request) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
return gitConnection.push(PushParams.create(request.getRemote())
.withRefSpec(request.getRefSpec())
.withForce(request.isForce())
.withTimeout(request.getTimeout())
.withUsername(request.getUsername())
.withPassword(request.getPassword()));
}
}
@PUT
@Path("remote")
@Consumes(MediaType.APPLICATION_JSON)
public void remoteAdd(RemoteAddRequest request) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
gitConnection.remoteAdd(RemoteAddParams.create(request.getName(), request.getUrl()).withBranches(request.getBranches()));
}
}
@DELETE
@Path("remote/{name}")
public void remoteDelete(@PathParam("name") String name) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
gitConnection.remoteDelete(name);
}
}
@GET
@Path("remote")
@Produces(MediaType.APPLICATION_JSON)
public List<Remote> remoteList(@QueryParam("remoteName") String remoteName, @QueryParam("verbose") boolean verbose)
throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
return gitConnection.remoteList(remoteName, verbose);
}
}
@POST
@Path("remote")
@Consumes(MediaType.APPLICATION_JSON)
public void remoteUpdate(RemoteUpdateRequest request) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
gitConnection.remoteUpdate(RemoteUpdateParams.create(request.getName())
.withRemoveUrl(request.getRemoveUrl())
.withRemovePushUrl(request.getRemovePushUrl())
.withAddUrl(request.getAddUrl())
.withAddPushUrl(request.getAddPushUrl())
.withBranches(request.getBranches())
.withAddBranches(request.isAddBranches()));
}
}
@POST
@Path("reset")
@Consumes(MediaType.APPLICATION_JSON)
public void reset(ResetRequest request) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
gitConnection.reset(ResetParams.create(request.getCommit(), request.getType()).withFilePattern(request.getFilePattern()));
}
}
@GET
@Path("status")
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
public Status status(@QueryParam("format") StatusFormat format) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
return gitConnection.status(format);
}
}
@POST
@Path("tag")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Tag tagCreate(TagCreateRequest request) throws ApiException {
GitConnection gitConnection = getGitConnection();
try {
return gitConnection.tagCreate(TagCreateParams.create(request.getName())
.withCommit(request.getCommit())
.withMessage(request.getMessage())
.withForce(request.isForce()));
} finally {
gitConnection.close();
}
}
@DELETE
@Path("tag/{name}")
public void tagDelete(@PathParam("name") String name) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
gitConnection.tagDelete(name);
}
}
@GET
@Path("tag")
@Produces(MediaType.APPLICATION_JSON)
public List<Tag> tagList(@QueryParam("pattern") String pattern) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
return gitConnection.tagList(pattern);
}
}
@GET
@Path("config")
@Produces(MediaType.APPLICATION_JSON)
public Map<String, String> getConfig(@QueryParam("requestedConfig") List<String> requestedConfig)
throws ApiException {
Map<String, String> result = new HashMap<>();
try (GitConnection gitConnection = getGitConnection()) {
Config config = gitConnection.getConfig();
if (requestedConfig == null || requestedConfig.isEmpty()) {
for (String row : config.getList()) {
String[] keyValues = row.split("=", 2);
result.put(keyValues[0], keyValues[1]);
}
} else {
for (String entry : requestedConfig) {
try {
String value = config.get(entry);
result.put(entry, value);
} catch (GitException exception) {
//value for this config property non found. Do nothing
}
}
}
}
return result;
}
@PUT
@Path("config")
@Consumes(MediaType.APPLICATION_JSON)
public void setConfig(ConfigRequest request) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
Config config = gitConnection.getConfig();
for (Map.Entry<String, String> configData : request.getConfigEntries().entrySet()) {
try {
config.set(configData.getKey(), configData.getValue());
} catch (GitException exception) {
final String msg = "Cannot write to config file";
LOG.error(msg, exception);
throw new GitException(msg);
}
}
}
}
@DELETE
@Path("config")
public void unsetConfig(@QueryParam("requestedConfig") List<String> requestedConfig) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
Config config = gitConnection.getConfig();
if (requestedConfig != null && !requestedConfig.isEmpty()) {
for (String entry : requestedConfig) {
try {
config.unset(entry);
} catch (GitException exception) {
//value for this config property non found. Do nothing
}
}
}
}
}
@GET
@Path("commiters")
public Commiters getCommiters(@Context UriInfo uriInfo) throws ApiException {
try (GitConnection gitConnection = getGitConnection()) {
return newDto(Commiters.class).withCommiters(gitConnection.getCommiters());
}
}
private String getAbsoluteProjectPath(String wsRelatedProjectPath) throws ApiException {
final RegisteredProject project = projectRegistry.getProject(wsRelatedProjectPath);
return project.getBaseFolder().getVirtualFile().toIoFile().getAbsolutePath();
}
private GitConnection getGitConnection() throws ApiException {
return gitConnectionFactory.getConnection(getAbsoluteProjectPath(projectPath));
}
}