|
|
@@ -6,8 +6,8 @@ import java.io.IOException;
|
|
|
import java.io.InputStream;
|
|
|
import java.io.InputStreamReader;
|
|
|
import java.io.OutputStream;
|
|
|
+import java.security.InvalidParameterException;
|
|
|
import java.util.ArrayDeque;
|
|
|
-import java.util.Collection;
|
|
|
import java.util.logging.Level;
|
|
|
import java.util.logging.Logger;
|
|
|
|
|
|
@@ -16,6 +16,7 @@ import info.knacki.pass.git.entities.GitObject;
|
|
|
import info.knacki.pass.git.entities.GitRef;
|
|
|
import info.knacki.pass.io.AppendableInputStream;
|
|
|
import info.knacki.pass.io.CharsetHelper;
|
|
|
+import info.knacki.pass.io.FileUtils;
|
|
|
import info.knacki.pass.io.OnErrorListener;
|
|
|
import info.knacki.pass.io.OnResponseListener;
|
|
|
import info.knacki.pass.io.OnStreamResponseListener;
|
|
|
@@ -23,60 +24,131 @@ import info.knacki.pass.io.ssh.SSHConnection;
|
|
|
import info.knacki.pass.io.ssh.SSHFactory;
|
|
|
import info.knacki.pass.settings.SettingsManager;
|
|
|
|
|
|
-public class SSHGitProtocol implements GitInterface {
|
|
|
+public class SSHGitProtocol extends BaseGitProtocol {
|
|
|
private final static Logger log = Logger.getLogger(SSHGitProtocol.class.getName());
|
|
|
- private final SettingsManager.Git fConfig;
|
|
|
+ private final SSHUrl fRepoUrl;
|
|
|
+
|
|
|
+ private final class ActiveSSHWrapper {
|
|
|
+ final SSHConnection fConnection;
|
|
|
+ final OutputStream fStdin;
|
|
|
+ final InputStream fStdout;
|
|
|
+ final InputStream fStderr;
|
|
|
+
|
|
|
+ ActiveSSHWrapper(SSHConnection con, OutputStream stdin, InputStream stdout, InputStream stderr) {
|
|
|
+ fConnection = con;
|
|
|
+ fStdin = stdin;
|
|
|
+ fStdout = stdout;
|
|
|
+ fStderr = stderr;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private ActiveSSHWrapper fActiveConnection = null;
|
|
|
+
|
|
|
+ private final class SSHUrl implements SSHConnection.ConnectionParams {
|
|
|
+ private final String repoHost;
|
|
|
+ final String repoName;
|
|
|
+
|
|
|
+ SSHUrl(String url) {
|
|
|
+ if (url.contains("://"))
|
|
|
+ url = url.substring(url.indexOf("://"));
|
|
|
+ int repoSep = url.indexOf(':');
|
|
|
+ if (repoSep < 0)
|
|
|
+ throw new InvalidParameterException("Cannot find repository name");
|
|
|
+ repoHost = url.substring(0, repoSep);
|
|
|
+ repoName = url.substring(repoSep + 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String GetHostname() {
|
|
|
+ return repoHost;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean HasAuthentication() {
|
|
|
+ return fConfig.HasAuthentication();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String GetUser() {
|
|
|
+ return fConfig.GetUser();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String GetPassword() {
|
|
|
+ return fConfig.GetPassword();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String GetPrivateKey() {
|
|
|
+ return fConfig.GetPrivateKey();
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
SSHGitProtocol(SettingsManager.Git config) {
|
|
|
- fConfig = config;
|
|
|
+ super(config);
|
|
|
+ fRepoUrl = new SSHUrl(config.GetUrl());
|
|
|
}
|
|
|
|
|
|
- private Collection<GitRef> GetRefs(SSHConnection con, OutputStream stdin, InputStream stdout) {
|
|
|
+ private byte[] ReadLine(InputStream in) throws IOException {
|
|
|
+ byte[] lineLenBytes = new byte[4];
|
|
|
+ if (in.read(lineLenBytes, 0, 4) < 4) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ int lineLen = 0;
|
|
|
+ try {
|
|
|
+ lineLen = Integer.parseInt(CharsetHelper.ByteArrayToString(lineLenBytes), 16);
|
|
|
+ }
|
|
|
+ catch (NumberFormatException e) {
|
|
|
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
|
+ stream.write(lineLenBytes);
|
|
|
+ byte[] tmp = new byte[1024];
|
|
|
+ int read = in.read(tmp);
|
|
|
+ stream.write(tmp, 0, read);
|
|
|
+ String error = CharsetHelper.ByteArrayToString(stream.toByteArray());
|
|
|
+ throw new NumberFormatException(error);
|
|
|
+ }
|
|
|
+ if (lineLen == 0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ lineLen -= 4;
|
|
|
+ if (lineLen == 0)
|
|
|
+ return new byte[] {};
|
|
|
+ byte[] line = new byte[lineLen];
|
|
|
+ if (in.read(line) != lineLen) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ for (int i =0; i < lineLen; ++i)
|
|
|
+ if (line[i] == 0)
|
|
|
+ lineLen = i;
|
|
|
+ return line;
|
|
|
+ }
|
|
|
+
|
|
|
+ private GitRef[] GetRefs(ActiveSSHWrapper sshConnection, OnErrorListener errorListener) {
|
|
|
ArrayDeque<GitRef> references = new ArrayDeque<>();
|
|
|
|
|
|
try {
|
|
|
- while (true) {
|
|
|
- byte[] hash = new byte[44];
|
|
|
- if (stdout.read(hash, 0, 4) < 4) {
|
|
|
- break;
|
|
|
- }
|
|
|
- if (CharsetHelper.ByteArrayToString(hash, 0, 4).equals("0000") && stdout.available() == 0) {
|
|
|
- break; // End Hash
|
|
|
- }
|
|
|
- // Read the remaining of the hash
|
|
|
- if (stdout.read(hash, 4, 40) < 40) {
|
|
|
- break;
|
|
|
- }
|
|
|
+ byte[] line;
|
|
|
+ while ((line = ReadLine(sshConnection.fStdout)) != null) {
|
|
|
+ GitRef ref = new GitRef(CharsetHelper.ByteArrayToString(line, 0, 40), CharsetHelper.ByteArrayToString(line, 41, line.length -42));
|
|
|
|
|
|
- ByteArrayOutputStream buffer = null;
|
|
|
- boolean ignoreToEol = false;
|
|
|
- // read the ref name
|
|
|
- while (true) {
|
|
|
- byte ch = (byte) stdout.read();
|
|
|
- if (ch == 0)
|
|
|
- ignoreToEol = true; // ignore server caps
|
|
|
- if (ch == '\n')
|
|
|
- break;
|
|
|
- if (buffer == null)
|
|
|
- buffer = new ByteArrayOutputStream();
|
|
|
- else if (!ignoreToEol)
|
|
|
- buffer.write(ch);
|
|
|
- }
|
|
|
- String branchRef;
|
|
|
- if (buffer != null && (branchRef = CharsetHelper.ByteArrayToString(buffer.toByteArray())).startsWith("refs/heads/")){
|
|
|
- GitRef ref = new GitRef(CharsetHelper.ByteArrayToString(hash), branchRef);
|
|
|
+ if (ref.GetBranch().startsWith("refs/heads/")) {
|
|
|
references.add(ref);
|
|
|
log.info("Found ref: " + ref);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- catch (IOException e) {
|
|
|
- log.log(Level.SEVERE, "Cannot read from ssh command: " +e.getMessage(), e);
|
|
|
+ catch (IOException | NumberFormatException e) {
|
|
|
+ final String errorMsg = "Cannot read from ssh command: " +e.getMessage();
|
|
|
+ log.log(Level.SEVERE, errorMsg, e);
|
|
|
+ errorListener.OnError(errorMsg, e);
|
|
|
+ return null;
|
|
|
}
|
|
|
- return references;
|
|
|
+ GitRef[] refs = new GitRef[references.size()];
|
|
|
+ return references.toArray(refs);
|
|
|
}
|
|
|
|
|
|
- public void ListenForErrors(InputStream stderr, OnErrorListener OnError) {
|
|
|
+ private void ListenForErrors(InputStream stderr, OnErrorListener OnError) {
|
|
|
(new Thread() {
|
|
|
@Override
|
|
|
public void run() {
|
|
|
@@ -97,43 +169,151 @@ public class SSHGitProtocol implements GitInterface {
|
|
|
|
|
|
@Override
|
|
|
public void GetRefs(OnResponseListener<GitRef[]> callback) {
|
|
|
- AppendableInputStream stdin = new AppendableInputStream();
|
|
|
+ if (fActiveConnection != null) {
|
|
|
+ GitRef[] refs = GetRefs(fActiveConnection, callback);
|
|
|
+ if (refs != null)
|
|
|
+ callback.OnResponse(refs);
|
|
|
+ } else {
|
|
|
+ AppendableInputStream stdin = new AppendableInputStream();
|
|
|
|
|
|
- SSHFactory
|
|
|
- .createInstance(fConfig, "git-upload-pack isundil/pass") // FIXME tmp
|
|
|
- .SetOnConnectionReadyListener((connection, stdout, stderr) -> {
|
|
|
- ListenForErrors(stderr, (msg, exc) -> log.severe(msg));
|
|
|
- Collection<GitRef> references = GetRefs(connection, stdin.GetWriter(), stdout);
|
|
|
- stdin.closeUnsafe();
|
|
|
- GitRef[] refs = new GitRef[references.size()];
|
|
|
- callback.OnResponse(references.toArray(refs));
|
|
|
- connection.disconnect();
|
|
|
- })
|
|
|
- .connect();
|
|
|
+ SSHFactory
|
|
|
+ .createInstance(fRepoUrl, "git-upload-pack " + fRepoUrl.repoName)
|
|
|
+ .SetOnConnectionReadyListener((connection, stdout, stderr) -> {
|
|
|
+ ListenForErrors(stderr, (msg, exc) -> log.severe(msg));
|
|
|
+ GitRef[] refs = GetRefs(new ActiveSSHWrapper(connection, stdin.GetWriter(), stdout, stderr), callback);
|
|
|
+ connection.disconnect();
|
|
|
+ if (refs != null)
|
|
|
+ callback.OnResponse(refs);
|
|
|
+ })
|
|
|
+ .connect();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
- public void FetchCommit(GitRef ref, OnResponseListener<GitCommit> response) {
|
|
|
+ private byte[] GitLine(String line) {
|
|
|
+ String lineResult;
|
|
|
+ if (line == null) {
|
|
|
+ lineResult = "0000";
|
|
|
+ } else {
|
|
|
+ lineResult = String.format("%04x%s\n", line.length() +5, line);
|
|
|
+ }
|
|
|
+ return CharsetHelper.StringToByteArray(lineResult);
|
|
|
+ }
|
|
|
|
|
|
+ private byte[] PullPackData(ActiveSSHWrapper sshWrapper, String hash, OnStreamResponseListener<byte[]> listener) {
|
|
|
+ try {
|
|
|
+ ListenForErrors(sshWrapper.fStderr, (msg, e) -> listener.OnMsg(msg));
|
|
|
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
|
+ out.write(GitLine("want " +hash));
|
|
|
+ out.write(GitLine("deepen 1"));
|
|
|
+ out.write(GitLine(null));
|
|
|
+ out.write(GitLine("done"));
|
|
|
+ log.severe(CharsetHelper.ByteArrayToString(out.toByteArray()));
|
|
|
+ sshWrapper.fStdin.write(out.toByteArray());
|
|
|
+ String line;
|
|
|
+ do {
|
|
|
+ byte[] b = ReadLine(sshWrapper.fStdout);
|
|
|
+ line = b == null ? null : CharsetHelper.ByteArrayToString(b);
|
|
|
+ } while (line == null || !"NAK\n".equals(line));
|
|
|
+ return FileUtils.ReadAllStream(sshWrapper.fStdout);
|
|
|
+ }
|
|
|
+ catch (IOException e) {
|
|
|
+ log.log(Level.SEVERE, "Cannot get hash " +hash +": " +e.getMessage(), e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
- public void FetchTree(GitCommit ci, OnStreamResponseListener<GitObject.GitTree> response) {
|
|
|
+ private void PullPackData(String hash, OnStreamResponseListener<byte[]> response) {
|
|
|
+ if (fActiveConnection != null) {
|
|
|
+ byte[] result = PullPackData(fActiveConnection, hash, response);
|
|
|
+ response.OnResponse(result);
|
|
|
+ } else {
|
|
|
+ AppendableInputStream stdin = new AppendableInputStream();
|
|
|
|
|
|
+ SSHFactory
|
|
|
+ .createInstance(fRepoUrl, "git-upload-pack " + fRepoUrl.repoName)
|
|
|
+ .SetOnConnectionReadyListener((connection, stdout, stderr) -> {
|
|
|
+ ListenForErrors(stderr, (msg, exc) -> log.severe(msg));
|
|
|
+ byte[] data = PullPackData(new ActiveSSHWrapper(connection, stdin.GetWriter(), stdout, stderr), hash, response);
|
|
|
+ connection.disconnect();
|
|
|
+ response.OnResponse(data);
|
|
|
+ })
|
|
|
+ .connect();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public void FetchHead(OnStreamResponseListener<GitCommit> response) {
|
|
|
+ public void FetchCommit(GitRef headRef, OnStreamResponseListener<GitCommit> response) {
|
|
|
+ PullPackData(headRef.GetHash(), new OnStreamResponseListener<byte[]>() {
|
|
|
+ @Override
|
|
|
+ public void OnMsg(String message) {
|
|
|
+ response.OnMsg(message);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void OnResponse(byte[] packData) {
|
|
|
+ try {
|
|
|
+ Pacman pacman = new PacmanBuilder().build(packData);
|
|
|
+ OnMsg("Read " +pacman.fPacked.size() +" objects from pack file");
|
|
|
+ response.OnResponse(pacman.BuildCommit(headRef.GetHash()));
|
|
|
+ }
|
|
|
+ catch (PacmanBuilder.InvalidPackException e) {
|
|
|
+ response.OnError("Cannot read pack object: " +e.getMessage(), e);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
+ @Override
|
|
|
+ public void OnError(String msg, Throwable e) {
|
|
|
+ response.OnError(msg, e);
|
|
|
+ }
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public void FetchBlob(GitObject.GitBlob blob, OnResponseListener<byte[]> response) {
|
|
|
+ public void PushBlobs(GitCommit.Builder commitBuilder, OnStreamResponseListener<Void> resp) {
|
|
|
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public void PushBlobs(GitCommit.Builder commitBuilder, OnStreamResponseListener<Void> resp) {
|
|
|
+ public void FetchBlob(GitObject.GitBlob blob, OnStreamResponseListener<byte[]> response) {
|
|
|
+ if (blob instanceof GitObject.GitRawData)
|
|
|
+ response.OnResponse(((GitObject.GitRawData) blob).GetData());
|
|
|
+ else
|
|
|
+ response.OnError("Unsupported object " +blob.GetHash(), new InvalidParameterException());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void FetchHead(OnStreamResponseListener<GitCommit> response) {
|
|
|
+ AppendableInputStream stdin = new AppendableInputStream();
|
|
|
+
|
|
|
+ SSHFactory
|
|
|
+ .createInstance(fRepoUrl, "git-upload-pack " +fRepoUrl.repoName)
|
|
|
+ .SetOnConnectionReadyListener((connection, stdout, stderr) -> {
|
|
|
+ fActiveConnection = new ActiveSSHWrapper(connection, stdin.GetWriter(), stdout, stderr);
|
|
|
+ GitRef ref = GetHeadRef(GetRefs(fActiveConnection, response));
|
|
|
+ if (ref == null)
|
|
|
+ return;
|
|
|
+ GetHeadCommitFromRef(ref, new OnStreamResponseListener<GitCommit>() {
|
|
|
+ @Override
|
|
|
+ public void OnMsg(String message) {
|
|
|
+ response.OnMsg(message);
|
|
|
+ }
|
|
|
|
|
|
+ @Override
|
|
|
+ public void OnResponse(GitCommit result) {
|
|
|
+ fActiveConnection.fConnection.disconnect();
|
|
|
+ fActiveConnection = null;
|
|
|
+ response.OnResponse(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void OnError(String msg, Throwable e) {
|
|
|
+ fActiveConnection.fConnection.disconnect();
|
|
|
+ fActiveConnection = null;
|
|
|
+ response.OnError(msg, e);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ })
|
|
|
+ .connect(stdin);
|
|
|
}
|
|
|
}
|