Skip to content

Commit f5439c4

Browse files
committed
bug git-as-svn#17: commit ignores git pre-receive hook
Executing git push with command line for commit hooks working
1 parent ef1e40f commit f5439c4

File tree

3 files changed

+131
-50
lines changed

3 files changed

+131
-50
lines changed

git-as-svn/src/main/java/svnserver/repository/git/GitHelper.java

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,37 @@
11
package svnserver.repository.git;
22

3-
import org.eclipse.jgit.lib.Constants;
4-
import org.eclipse.jgit.lib.FileMode;
3+
import org.eclipse.jgit.api.Git;
4+
import org.eclipse.jgit.api.errors.GitAPIException;
5+
import org.eclipse.jgit.lib.*;
6+
import org.eclipse.jgit.transport.PushResult;
7+
import org.eclipse.jgit.transport.RefSpec;
8+
import org.eclipse.jgit.transport.RemoteRefUpdate;
59
import org.jetbrains.annotations.NotNull;
10+
import org.slf4j.Logger;
11+
import org.slf4j.LoggerFactory;
12+
import org.tmatesoft.svn.core.SVNErrorCode;
13+
import org.tmatesoft.svn.core.SVNErrorMessage;
14+
import org.tmatesoft.svn.core.SVNException;
615
import org.tmatesoft.svn.core.SVNNodeKind;
716

17+
import java.io.BufferedReader;
18+
import java.io.IOException;
19+
import java.io.InputStreamReader;
20+
import java.nio.charset.StandardCharsets;
21+
822
/**
923
* Usefull methods.
1024
*
1125
* @author Artem V. Navrotskiy <[email protected]>
1226
*/
1327
public final class GitHelper {
28+
@NotNull
29+
private static final Logger log = LoggerFactory.getLogger(GitRepository.class);
30+
@NotNull
31+
private static final String HOOK_MESSAGE_PREFIX = "remote:";
32+
@NotNull
33+
private static final String SYSTEM_MESSAGE_PREFIX = "!";
34+
1435
private GitHelper() {
1536
}
1637

@@ -24,6 +45,81 @@ public static SVNNodeKind getKind(@NotNull FileMode fileMode) {
2445
default:
2546
throw new IllegalStateException("Unknown obj type: " + objType);
2647
}
48+
}
2749

50+
public static boolean pushLocal(@NotNull Repository repository, @NotNull ObjectId commitId, @NotNull Ref branch) throws SVNException {
51+
try {
52+
final Iterable<PushResult> results = new Git(repository)
53+
.push()
54+
.setRemote(".")
55+
.setRefSpecs(new RefSpec(commitId.name() + ":" + branch.getName()))
56+
.call();
57+
for (PushResult result : results) {
58+
for (RemoteRefUpdate remoteUpdate : result.getRemoteUpdates()) {
59+
switch (remoteUpdate.getStatus()) {
60+
case REJECTED_NONFASTFORWARD:
61+
return false;
62+
case OK:
63+
break;
64+
default:
65+
log.error("Unexpected push error: {}", remoteUpdate);
66+
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_WRITE_ERROR, remoteUpdate.toString()));
67+
}
68+
}
69+
}
70+
return true;
71+
} catch (GitAPIException e) {
72+
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_WRITE_ERROR, e));
73+
}
74+
}
75+
76+
public static boolean pushNative(@NotNull Repository repository, @NotNull ObjectId commitId, @NotNull Ref branch) throws IOException, SVNException {
77+
try {
78+
repository.getDirectory();
79+
final ProcessBuilder processBuilder = new ProcessBuilder("git", "push", "--porcelain", "--quiet", ".", commitId.name() + ":" + branch.getName())
80+
.directory(repository.getDirectory())
81+
.redirectErrorStream(true);
82+
processBuilder.environment().put("LANG", "en_US.utf8");
83+
final Process process = processBuilder.start();
84+
final StringBuilder resultBuilder = new StringBuilder();
85+
final StringBuilder hookBuilder = new StringBuilder();
86+
try (final BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
87+
while (true) {
88+
final String line = stdout.readLine();
89+
if (line == null) {
90+
break;
91+
}
92+
if (line.startsWith(HOOK_MESSAGE_PREFIX)) {
93+
if (hookBuilder.length() > 0) hookBuilder.append('\n');
94+
hookBuilder.append(line.substring(HOOK_MESSAGE_PREFIX.length() + 1));
95+
}
96+
if (line.startsWith(SYSTEM_MESSAGE_PREFIX)) {
97+
// System message like:
98+
// ! 2d1ed4dcc45bef07f6dfffabe7d3ff53aa147705:refs/heads/local [remote rejected] (pre-receive hook declined)
99+
// ! 75cad4dcb5f6982a1f2df073157f3aa2083ae272:refs/heads/local [rejected] (non-fast-forward)
100+
if (resultBuilder.length() > 0) resultBuilder.append('\n');
101+
resultBuilder.append(line.substring(SYSTEM_MESSAGE_PREFIX.length() + 1));
102+
}
103+
}
104+
}
105+
int exitCode = process.waitFor();
106+
if (exitCode == 0) {
107+
return true;
108+
}
109+
final String resultMessage = resultBuilder.toString();
110+
if (resultMessage.contains("non-fast-forward")) {
111+
return false;
112+
}
113+
if (resultMessage.contains("hook")) {
114+
final String hookMessage = hookBuilder.toString();
115+
log.warn("Push rejected by hook:\n{}", hookMessage);
116+
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.REPOS_HOOK_FAILURE, "Commit blocked by hook with output:\n" + hookMessage));
117+
}
118+
log.error("Unknown git push result:\n{}", resultMessage);
119+
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_WRITE_ERROR, resultMessage));
120+
} catch (InterruptedException e) {
121+
e.printStackTrace();
122+
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_WRITE_ERROR, e));
123+
}
28124
}
29125
}

git-as-svn/src/main/java/svnserver/repository/git/GitRepository.java

Lines changed: 29 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
package svnserver.repository.git;
22

3-
import org.eclipse.jgit.api.Git;
4-
import org.eclipse.jgit.api.errors.GitAPIException;
53
import org.eclipse.jgit.internal.storage.file.FileRepository;
64
import org.eclipse.jgit.lib.*;
75
import org.eclipse.jgit.revwalk.RevCommit;
86
import org.eclipse.jgit.revwalk.RevWalk;
9-
import org.eclipse.jgit.transport.PushResult;
10-
import org.eclipse.jgit.transport.RefSpec;
11-
import org.eclipse.jgit.transport.RemoteRefUpdate;
127
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
138
import org.jetbrains.annotations.NotNull;
149
import org.jetbrains.annotations.Nullable;
@@ -19,7 +14,10 @@
1914
import org.tmatesoft.svn.core.SVNException;
2015
import svnserver.StringHelper;
2116
import svnserver.auth.User;
22-
import svnserver.repository.*;
17+
import svnserver.repository.VcsCommitBuilder;
18+
import svnserver.repository.VcsDeltaConsumer;
19+
import svnserver.repository.VcsFile;
20+
import svnserver.repository.VcsRepository;
2321

2422
import java.io.File;
2523
import java.io.IOException;
@@ -49,6 +47,9 @@ public class GitRepository implements VcsRepository {
4947
private final Map<String, int[]> lastUpdates = new ConcurrentHashMap<>();
5048
@NotNull
5149
private final ReadWriteLock lock = new ReentrantReadWriteLock();
50+
// Lock for prevent concurrent pushes.
51+
@NotNull
52+
private final Object pushLock = new Object();
5253
@NotNull
5354
private final String uuid;
5455
@NotNull
@@ -385,48 +386,31 @@ public void delete(@NotNull String name, @NotNull VcsFile file) throws SVNExcept
385386

386387
@Override
387388
public GitRevision commit(@NotNull User userInfo, @NotNull String message) throws SVNException, IOException {
388-
final GitTreeUpdate root = treeStack.element();
389-
final ObjectId treeId = root.buildTree(inserter);
390-
log.info("Create tree {} for commit.", treeId.name());
391-
392-
final CommitBuilder commitBuilder = new CommitBuilder();
393-
final PersonIdent ident = createIdent(userInfo);
394-
commitBuilder.setAuthor(ident);
395-
commitBuilder.setCommitter(ident);
396-
commitBuilder.setMessage(message);
397-
commitBuilder.setParentId(commit.getId());
398-
commitBuilder.setTreeId(treeId);
399-
final ObjectId commitId = inserter.insert(commitBuilder);
400-
401-
log.info("Create commit {}: {}", commitId.name(), message);
402-
403-
try {
404-
log.info("Try to push commit in branch: {}", branchRef);
405-
Iterable<PushResult> results = new Git(repository)
406-
.push()
407-
.setRemote(".")
408-
.setRefSpecs(new RefSpec(commitId.name() + ":" + branchRef.getName()))
409-
.call();
410-
for (PushResult result : results) {
411-
for (RemoteRefUpdate remoteUpdate : result.getRemoteUpdates()) {
412-
switch (remoteUpdate.getStatus()) {
413-
case REJECTED_NONFASTFORWARD:
414-
log.info("Non fast forward push rejected");
415-
return null;
416-
case OK:
417-
break;
418-
default:
419-
log.error("Unexpected push error: {}", remoteUpdate);
420-
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_WRITE_ERROR, remoteUpdate.toString()));
421-
}
422-
}
389+
synchronized (pushLock) {
390+
final GitTreeUpdate root = treeStack.element();
391+
final ObjectId treeId = root.buildTree(inserter);
392+
log.info("Create tree {} for commit.", treeId.name());
393+
394+
final CommitBuilder commitBuilder = new CommitBuilder();
395+
final PersonIdent ident = createIdent(userInfo);
396+
commitBuilder.setAuthor(ident);
397+
commitBuilder.setCommitter(ident);
398+
commitBuilder.setMessage(message);
399+
commitBuilder.setParentId(commit.getId());
400+
commitBuilder.setTreeId(treeId);
401+
final ObjectId commitId = inserter.insert(commitBuilder);
402+
403+
log.info("Create commit {}: {}", commitId.name(), message);
404+
log.info("Try to push commit in branch: {}", branchRef.getName());
405+
if (!GitHelper.pushNative(repository, commitId, branchRef)) {
406+
log.info("Non fast forward push rejected");
407+
return null;
423408
}
424409
log.info("Commit is pushed");
425-
} catch (GitAPIException e) {
426-
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_WRITE_ERROR, e));
410+
411+
updateRevisions();
412+
return getRevision(commitId);
427413
}
428-
updateRevisions();
429-
return getRevision(commitId);
430414
}
431415

432416
private PersonIdent createIdent(User userInfo) {

git-as-svn/src/main/java/svnserver/server/command/CommitCmd.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -357,12 +357,16 @@ private void closeDir(@NotNull SessionContext context, @NotNull TokenParams args
357357
}
358358

359359
private void closeEdit(@NotNull SessionContext context, @NotNull NoParams args) throws IOException, SVNException {
360+
if (context.getUser().isAnonymous()) {
361+
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.RA_NOT_AUTHORIZED, "Anonymous users cannot create commits"));
362+
}
360363
for (int pass = 0; ; ++pass) {
361364
if (pass >= MAX_PASS_COUNT) {
362365
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.CANCELLED, "Cant commit changes to upstream repositroy."));
363366
}
364367
final VcsRevision revision = updateDir(context.getRepository().createCommitBuilder(), "").commit(context.getUser(), message);
365368
if (revision != null) {
369+
// todo: CheckPermissionStep must be before commit
366370
context.push(new CheckPermissionStep((svnContext) -> complete(svnContext, revision)));
367371
break;
368372
}
@@ -377,9 +381,6 @@ private void closeEdit(@NotNull SessionContext context, @NotNull NoParams args)
377381
}
378382

379383
private void complete(@NotNull SessionContext context, @NotNull VcsRevision revision) throws IOException, SVNException {
380-
if (context.getUser().isAnonymous())
381-
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.RA_NOT_AUTHORIZED, "Anonymous users cannot create commits"));
382-
383384
final SvnServerWriter writer = context.getWriter();
384385
writer
385386
.listBegin()

0 commit comments

Comments
 (0)