/**
* This file is part of git-as-svn. It is subject to the license terms
* in the LICENSE file found in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/gpl-2.0.html. No part of git-as-svn,
* including this file, may be copied, modified, propagated, or distributed
* except according to the terms contained in the LICENSE file.
*/
package svnserver.repository.git.push;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import svnserver.auth.User;
import svnserver.auth.UserDB;
import svnserver.context.LocalContext;
import svnserver.context.SharedContext;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
/**
* Git push by native git client.
*
* @author Artem V. Navrotskiy <bozaro@users.noreply.github.com>
*/
public class GitPushNative implements GitPusher {
@NotNull
private static final Logger log = LoggerFactory.getLogger(GitPushNativeConfig.class);
@NotNull
private static final String HOOK_MESSAGE_PREFIX = "remote:";
@NotNull
private static final String SYSTEM_MESSAGE_PREFIX = "!";
@NotNull
private final SharedContext context;
public GitPushNative(@NotNull LocalContext context) {
this.context = context.getShared();
}
@Override
public boolean push(@NotNull Repository repository, @NotNull ObjectId commitId, @NotNull String branch, @NotNull User userInfo) throws SVNException, IOException {
try {
repository.getDirectory();
final ProcessBuilder processBuilder = new ProcessBuilder("git", "push", "--porcelain", "--quiet", ".", commitId.name() + ":" + branch)
.directory(repository.getDirectory())
.redirectErrorStream(true);
processBuilder.environment().put("LANG", "en_US.utf8");
userInfo.updateEnvironment(processBuilder.environment());
context.sure(UserDB.class).updateEnvironment(processBuilder.environment(), userInfo);
final Process process = processBuilder.start();
final StringBuilder resultBuilder = new StringBuilder();
final StringBuilder hookBuilder = new StringBuilder();
try (final BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
while (true) {
final String line = stdout.readLine();
if (line == null) {
break;
}
if (line.startsWith(HOOK_MESSAGE_PREFIX)) {
if (hookBuilder.length() > 0) hookBuilder.append('\n');
hookBuilder.append(line.substring(HOOK_MESSAGE_PREFIX.length() + 1));
}
if (line.startsWith(SYSTEM_MESSAGE_PREFIX)) {
// System message like:
// ! 2d1ed4dcc45bef07f6dfffabe7d3ff53aa147705:refs/heads/local [remote rejected] (pre-receive hook declined)
// ! 75cad4dcb5f6982a1f2df073157f3aa2083ae272:refs/heads/local [rejected] (non-fast-forward)
if (resultBuilder.length() > 0) resultBuilder.append('\n');
resultBuilder.append(line.substring(SYSTEM_MESSAGE_PREFIX.length() + 1));
}
}
}
int exitCode = process.waitFor();
if (exitCode == 0) {
return true;
}
final String resultMessage = resultBuilder.toString();
if (resultMessage.contains("non-fast-forward")) {
return false;
}
if (resultMessage.contains("hook")) {
final String hookMessage = hookBuilder.toString();
log.warn("Push rejected by hook:\n{}", hookMessage);
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.REPOS_HOOK_FAILURE, "Commit blocked by hook with output:\n" + hookMessage));
}
log.error("Unknown git push result:\n{}", resultMessage);
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_WRITE_ERROR, resultMessage));
} catch (InterruptedException e) {
e.printStackTrace();
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_WRITE_ERROR, e));
}
}
}