/**
* Yobi, Project Hosting SW
*
* Copyright 2012 NAVER Corp.
* http://yobi.io
*
* @Author Yi EungJun
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package playRepository;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import models.*;
import org.codehaus.jackson.node.ObjectNode;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.PacketLineOut;
import org.eclipse.jgit.transport.PostReceiveHook;
import org.eclipse.jgit.transport.PostReceiveHookChain;
import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
import org.eclipse.jgit.transport.UploadPack;
import org.tigris.subversion.javahl.ClientException;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.internal.server.dav.DAVServlet;
import play.Logger;
import play.mvc.Http.RawBuffer;
import play.mvc.Http.Request;
import play.mvc.Http.Response;
import playRepository.hooks.*;
import controllers.ProjectApp;
import controllers.UserApp;
public class RepositoryService {
public static final String VCS_SUBVERSION = "Subversion";
public static final String VCS_GIT = "GIT";
public static Map<String, String> vcsTypes() {
HashMap<String, String> map = new HashMap<>();
map.put(VCS_GIT, "project.new.vcsType.git");
map.put(VCS_SUBVERSION, "project.new.vcsType.subversion");
return map;
}
/**
* @see {@link ProjectApp#deleteProject(String, String)}
* @see {@link playRepository.PlayRepository#delete()}
*/
public static void deleteRepository(Project project)
throws IOException, ServletException {
RepositoryService.getRepository(project).delete();
}
/**
* @see {@link #deleteRepository(Project)}
* @see {@link PlayRepository#create()}
*/
public static void createRepository(Project project) throws IOException, ServletException,
ClientException, UnsupportedOperationException {
RepositoryService.deleteRepository(project);
RepositoryService.getRepository(project).create();
}
public static List<ObjectNode> getMetaDataFromAncestorDirectories(PlayRepository repository,
String branch,
String path) throws
SVNException, GitAPIException, IOException {
List<ObjectNode> recursiveData = new ArrayList<>();
String partialPath = "";
String[] pathArray = path.split("/");
Integer pathLength = pathArray.length;
ObjectNode metaData;
for(int i = 0; i < pathLength; i++){
metaData = repository.getMetaDataFromPath(branch, partialPath);
metaData.put("path", partialPath);
partialPath = (partialPath.equals("")) ? pathArray[i] : partialPath + "/" + pathArray[i];
recursiveData.add(metaData);
}
return recursiveData;
}
/**
* @see {@link PlayRepository#getRawFile(String, String)}
*/
public static byte[] getFileAsRaw(String userName, String projectName, String revision, String path)
throws UnsupportedOperationException, IOException, ServletException, SVNException {
Project project = Project.findByOwnerAndProjectName(userName, projectName);
return RepositoryService.getRepository(project, true).getRawFile(revision, path);
}
public static PlayRepository getRepository(Project project, boolean alternatesMergeRepo) throws IOException,
ServletException, UnsupportedOperationException {
if (project == null) {
return null;
}
switch (project.vcs) {
case VCS_GIT:
return new GitRepository(project.owner, project.name, alternatesMergeRepo);
case VCS_SUBVERSION:
return new SVNRepository(project.owner, project.name);
default:
throw new UnsupportedOperationException();
}
}
public static PlayRepository getRepository(Project project) throws IOException,
ServletException, UnsupportedOperationException {
return getRepository(project, true);
}
public static PlayRepository getRepository(String userName, String projectName) throws IOException,
ServletException, UnsupportedOperationException {
Project project = Project.findByOwnerAndProjectName(userName, projectName);
return RepositoryService.getRepository(project);
}
public static DAVServlet createDavServlet(final String userName) throws ServletException {
DAVServlet servlet = new DAVServlet();
servlet.init(new ServletConfig() {
@Override
public String getInitParameter(String name) {
if (name.equals("SVNParentPath")) {
return new File(SVNRepository.getRepoPrefix() + userName + "/").getAbsolutePath();
} else {
return play.Configuration.root().getString("application." + name);
}
}
@Override
public Enumeration<String> getInitParameterNames() {
throw new UnsupportedOperationException();
}
@Override
public ServletContext getServletContext() {
throw new UnsupportedOperationException();
}
@Override
public String getServletName() {
throw new UnsupportedOperationException();
}
});
return servlet;
}
/**
* @see <a href="https://www.kernel.org/pub/software/scm/git/docs/git-upload-pack.html">git-upload-pack</a>
* @see <a href="https://www.kernel.org/pub/software/scm/git/docs/git-receive-pack.html">git-receive-pack</a>
*/
public static byte[] gitAdvertise(Project project, String service, Response response) throws IOException {
response.setContentType("application/x-" + service + "-advertisement");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
PacketLineOut packetLineOut = new PacketLineOut(byteArrayOutputStream);
packetLineOut.writeString("# service=" + service + "\n");
packetLineOut.end();
PacketLineOutRefAdvertiser packetLineOutRefAdvertiser = new PacketLineOutRefAdvertiser(packetLineOut);
if (service.equals("git-upload-pack")) {
Repository repository = GitRepository.buildGitRepository(project);
UploadPack uploadPack = new UploadPack(repository);
uploadPack.setBiDirectionalPipe(false);
uploadPack.sendAdvertisedRefs(packetLineOutRefAdvertiser);
} else if (service.equals("git-receive-pack")) {
Repository repository = GitRepository.buildGitRepository(project, false);
ReceivePack receivePack = new ReceivePack(repository);
receivePack.sendAdvertisedRefs(packetLineOutRefAdvertiser);
}
byteArrayOutputStream.close();
return byteArrayOutputStream.toByteArray();
}
/**
* @see <a href="https://www.kernel.org/pub/software/scm/git/docs/git-upload-pack.html">git-upload-pack</a>
* @see <a href="https://www.kernel.org/pub/software/scm/git/docs/git-receive-pack.html">git-receive-pack</a>
*/
public static PipedInputStream gitRpc(final Project project, String service, Request request, Response response) {
response.setContentType("application/x-" + service + "-result");
RawBuffer raw = request.body().asRaw();
byte[] buf = raw.asBytes();
InputStream requestStream = null;
try {
// If the content size is bigger than memoryThreshold,
// which is defined as 100 * 1024 in play.api.mvc.BodyParsers trait,
// the content is stored as a file.
if (buf != null) {
requestStream = new ByteArrayInputStream(buf);
} else {
requestStream = new FileInputStream(raw.asFile());
}
Repository repository;
PipedInputStream responseStream = new PipedInputStream();
switch (service) {
case "git-upload-pack":
repository = GitRepository.buildGitRepository(project);
uploadPack(requestStream, repository, new PipedOutputStream(responseStream));
break;
case "git-receive-pack":
repository = GitRepository.buildGitRepository(project, false);
PostReceiveHook postReceiveHook = createPostReceiveHook(UserApp.currentUser(), project, request);
receivePack(requestStream, repository, new PipedOutputStream(responseStream),
postReceiveHook);
// receivePack.setEchoCommandFailures(true);
break;
default:
requestStream.close();
break;
}
return responseStream;
} catch (IOException e) {
if(requestStream != null) {
try { requestStream.close(); } catch (IOException e1) {
Logger.error("failed to close request stream", e1);
}
}
throw new RuntimeException(e);
}
}
private static PostReceiveHook createPostReceiveHook(
final User currentUser, final Project project, final Request request) {
List<PostReceiveHook> hooks = new ArrayList<>();
hooks.add(new UpdateLastPushedDate(project));
hooks.add(new UpdateRecentlyPushedBranch(project));
hooks.add(new IssueReferredFromCommitEvent(project, currentUser));
hooks.add(new PullRequestCheck(currentUser, request, project));
hooks.add(new NotifyPushedCommits(project, currentUser));
return PostReceiveHookChain.newChain(hooks);
}
private static void receivePack(final InputStream input, Repository repository,
final OutputStream output,
final PostReceiveHook postReceiveHook) {
final ReceivePack receivePack = new ReceivePack(repository);
receivePack.setBiDirectionalPipe(false);
new Thread() {
@Override
public void run() {
try {
receivePack.setPostReceiveHook(postReceiveHook);
receivePack.receive(input, output, null);
} catch (IOException e) {
Logger.error("receivePack failed", e);
}
closeStreams("receivePack", input, output);
}
}.start();
}
private static void uploadPack(final InputStream input, Repository repository,
final OutputStream output) {
final UploadPack uploadPack = new UploadPack(repository);
uploadPack.setBiDirectionalPipe(false);
new Thread() {
@Override
public void run() {
try {
uploadPack.upload(input, output, null);
} catch (IOException e) {
Logger.error("uploadPack failed", e);
}
closeStreams("uploadPack", input, output);
}
}.start();
}
private static void closeStreams(String serviceName, InputStream input, OutputStream output) {
try {
input.close();
} catch (IOException e) {
Logger.error(serviceName + ": Failed to close input stream", e);
}
try {
output.close();
} catch (IOException e) {
Logger.error(serviceName + ": Failed to close output stream", e);
}
}
}