|
|
@@ -1,18 +1,26 @@
|
|
|
package info.knacki.pass.git;
|
|
|
|
|
|
import android.os.AsyncTask;
|
|
|
+import android.util.Base64;
|
|
|
|
|
|
-import java.io.File;
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
+import java.io.DataOutputStream;
|
|
|
import java.io.IOException;
|
|
|
import java.io.InputStream;
|
|
|
-import java.math.BigInteger;
|
|
|
+import java.io.OutputStream;
|
|
|
import java.net.Authenticator;
|
|
|
+import java.net.HttpURLConnection;
|
|
|
import java.net.MalformedURLException;
|
|
|
import java.net.PasswordAuthentication;
|
|
|
import java.net.URL;
|
|
|
import java.net.URLConnection;
|
|
|
+import java.nio.ByteBuffer;
|
|
|
import java.nio.charset.Charset;
|
|
|
import java.util.ArrayList;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Locale;
|
|
|
import java.util.Map;
|
|
|
import java.util.logging.Level;
|
|
|
import java.util.logging.Logger;
|
|
|
@@ -20,10 +28,12 @@ import java.util.zip.InflaterInputStream;
|
|
|
|
|
|
import info.knacki.pass.git.entities.GitCommit;
|
|
|
import info.knacki.pass.git.entities.GitObject;
|
|
|
+import info.knacki.pass.git.entities.GitPackable;
|
|
|
+import info.knacki.pass.git.entities.GitPackableUtil;
|
|
|
import info.knacki.pass.git.entities.GitRef;
|
|
|
import info.knacki.pass.settings.SettingsManager;
|
|
|
|
|
|
-class HttpGitProtocol implements GitInterface {
|
|
|
+public class HttpGitProtocol implements GitInterface { // FIXME package-private
|
|
|
private final SettingsManager.Git fConfig;
|
|
|
private GitRef[] fRefsCache = null;
|
|
|
private static final Logger log = Logger.getLogger(HttpGitProtocol.class.getName());
|
|
|
@@ -104,6 +114,85 @@ class HttpGitProtocol implements GitInterface {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ static class PostTask extends AsyncTask<Void, Void, Integer> {
|
|
|
+ private final URL fUrl;
|
|
|
+ private final OnResponseListener<byte[]> fResp;
|
|
|
+ private final SettingsManager.Git fConfig;
|
|
|
+ private final OnResponseListener<OutputStream> fOnReadyWrite;
|
|
|
+ private final Map<String, String> fHeaders;
|
|
|
+
|
|
|
+ PostTask(URL url, SettingsManager.Git config, Map<String, String> headers, OnResponseListener<byte[]> resp, OnResponseListener<OutputStream> onReadyWrite) {
|
|
|
+ fUrl = url;
|
|
|
+ fResp = resp;
|
|
|
+ fConfig = config;
|
|
|
+ fOnReadyWrite = onReadyWrite;
|
|
|
+ fHeaders = headers;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected InputStream GetInputFilter(URLConnection in) throws IOException {
|
|
|
+ try {
|
|
|
+ return in.getInputStream();
|
|
|
+ } catch (Throwable e) {
|
|
|
+ Map<String, List<String>> headers = in.getHeaderFields();
|
|
|
+ lp: for (List<String> i: headers.values()) {
|
|
|
+ for (String status: (List<String>) i) {
|
|
|
+ log.severe(in.getURL().toString() + ": " + status);
|
|
|
+ break lp;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected void ManageResponse(byte[] resp) {
|
|
|
+ fResp.onResponse(resp);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected Integer doInBackground(Void... voids) {
|
|
|
+ try {
|
|
|
+ log.log(Level.INFO, "fetching " +fUrl.toString());
|
|
|
+ HttpURLConnection httpClient = (HttpURLConnection) fUrl.openConnection();
|
|
|
+ httpClient.setRequestMethod("POST");
|
|
|
+ for (Map.Entry<String, String> header: fHeaders.entrySet()) {
|
|
|
+ httpClient.setRequestProperty(header.getKey(), header.getValue());
|
|
|
+ }
|
|
|
+ if (fConfig.HasAuthentification()) {
|
|
|
+ httpClient.setRequestProperty("Authorization", "basic " +Base64.encode("fConfig.GetUser():fConfig.GetPassword()".getBytes(), Base64.NO_WRAP));
|
|
|
+ }
|
|
|
+ fOnReadyWrite.onResponse(httpClient.getOutputStream());
|
|
|
+ InputStream in = GetInputFilter(httpClient);
|
|
|
+ ArrayList<byte[]> fullBuffer = new ArrayList<>();
|
|
|
+ int totalRead = 0;
|
|
|
+ byte[] buffer = new byte[1024];
|
|
|
+ int currentRead;
|
|
|
+ while ((currentRead = in.read(buffer, 0, 1024)) != 0) {
|
|
|
+ fullBuffer.add(buffer);
|
|
|
+ buffer = new byte[1024];
|
|
|
+ totalRead += currentRead;
|
|
|
+ if (currentRead < 1024)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ in.close();
|
|
|
+ buffer = new byte[totalRead];
|
|
|
+ int i =0;
|
|
|
+ for (byte []currentBuf: fullBuffer) {
|
|
|
+ for (int j =0; j < currentBuf.length && i*1024+j < totalRead; ++j) {
|
|
|
+ buffer[i*1024 +j] = currentBuf[j];
|
|
|
+ }
|
|
|
+ ++i;
|
|
|
+ }
|
|
|
+ ManageResponse(buffer);
|
|
|
+ }
|
|
|
+ catch (IOException e) {
|
|
|
+ log.log(Level.WARNING, e.getMessage(), e);
|
|
|
+ fResp.onError(e.getClass().getSimpleName() +": " +e.getMessage(), e);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
static class DownloaderWithInflaterTask extends DownloaderTask {
|
|
|
DownloaderWithInflaterTask(URL url, SettingsManager.Git config, OnResponseListener<byte[]> resp) {
|
|
|
super(url, config, resp);
|
|
|
@@ -155,6 +244,10 @@ class HttpGitProtocol implements GitInterface {
|
|
|
new DownloaderWithInflaterTask(url, fConfig, callback).execute();
|
|
|
}
|
|
|
|
|
|
+ private void ProtoPost(URL url, Map<String, String> headers, OnResponseListener<byte[]> callback, OnResponseListener<OutputStream> onReadyWrite) {
|
|
|
+ new PostTask(url, fConfig, headers, callback, onReadyWrite).execute();
|
|
|
+ }
|
|
|
+
|
|
|
private void protoGet(final URL url, final OnResponseListener<String> callback) {
|
|
|
new StringDownloaderTask(url, fConfig, callback).execute();
|
|
|
}
|
|
|
@@ -193,7 +286,7 @@ class HttpGitProtocol implements GitInterface {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public void FetchCommit(GitRef ref, final OnResponseListener<GitCommit> response) {
|
|
|
+ public void FetchCommit(final GitRef ref, final OnResponseListener<GitCommit> response) {
|
|
|
PullHash(ref.GetHash(), new OnResponseListener<byte[]>() {
|
|
|
@Override
|
|
|
public void onResponse(byte[] result) {
|
|
|
@@ -214,12 +307,12 @@ class HttpGitProtocol implements GitInterface {
|
|
|
private final OnStreamResponseListener<GitObject.GitTree> fResponseListener;
|
|
|
|
|
|
private RecursiveFetchTreeWalker(GitCommit ci, final OnStreamResponseListener<GitObject.GitTree> responseListener) {
|
|
|
- fRoot = fCurrentTree = new GitObject.GitTree(ci.GetTree()).Initialize();
|
|
|
+ fRoot = fCurrentTree = ci.InitializeTree().Initialize();
|
|
|
fResponseListener = responseListener;
|
|
|
}
|
|
|
|
|
|
public void run() {
|
|
|
- PullHash(ci.GetTree());
|
|
|
+ PullHash(ci.GetTreeHash());
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@@ -242,6 +335,10 @@ class HttpGitProtocol implements GitInterface {
|
|
|
HttpGitProtocol.this.PullHash(hash, this);
|
|
|
}
|
|
|
|
|
|
+ private void PullHash(byte[] hash) {
|
|
|
+ PullHash(GitSha1.BytesToString(hash));
|
|
|
+ }
|
|
|
+
|
|
|
private void FillTree(final GitObject.GitTree tree, byte[] data) {
|
|
|
int i = 0;
|
|
|
|
|
|
@@ -262,26 +359,16 @@ class HttpGitProtocol implements GitInterface {
|
|
|
++len;
|
|
|
byte[] filenameBytes = new byte[len];
|
|
|
System.arraycopy(data, i, filenameBytes, 0, len);
|
|
|
- i += len;
|
|
|
String fileName = new String(filenameBytes, Charset.defaultCharset());
|
|
|
- if (i +21 > data.length)
|
|
|
+ i += len +1;
|
|
|
+ if (i +20 > data.length)
|
|
|
break;
|
|
|
- byte []sha1 = new byte[21];
|
|
|
- System.arraycopy(data, i, sha1, 0, 21);
|
|
|
- i += 21;
|
|
|
- tree.AddItem(GitObject.factory(tree, mode, fileName, sha1ToString(sha1)));
|
|
|
+ byte []sha1 = new byte[20];
|
|
|
+ System.arraycopy(data, i, sha1, 0, 20);
|
|
|
+ i += 20;
|
|
|
+ tree.AddItem(GitObject.factory(tree, mode, fileName, sha1));
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- private String sha1ToString(byte[] sha1) {
|
|
|
- String out = new BigInteger(1, sha1).toString(16);
|
|
|
- if (out.length() == 40)
|
|
|
- return out;
|
|
|
- StringBuilder sb = new StringBuilder();
|
|
|
- for (int i = 40 -out.length(); i > 0; --i)
|
|
|
- sb.append('0');
|
|
|
- return sb.toString() +out;
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
new RecursiveFetchTreeWalker(ci, response).run();
|
|
|
@@ -289,10 +376,10 @@ class HttpGitProtocol implements GitInterface {
|
|
|
|
|
|
@Override
|
|
|
public void FetchBlob(final GitObject.GitBlob blob, final OnResponseListener<byte[]> response) {
|
|
|
- PullHash(blob.GetHash(), response);
|
|
|
+ PullHash(GitSha1.BytesToString(blob.GetHash()), response);
|
|
|
}
|
|
|
|
|
|
- public void FetchTree(final OnStreamResponseListener<GitObject.GitTree> response) {
|
|
|
+ public void FetchHead(final OnStreamResponseListener<GitCommit> response) {
|
|
|
GetRefs(new GitInterface.OnResponseListener<GitRef[]>() {
|
|
|
@Override
|
|
|
public void onResponse(final GitRef[] result) {
|
|
|
@@ -307,10 +394,10 @@ class HttpGitProtocol implements GitInterface {
|
|
|
response.onMsg("Checking out branch " +myref.GetBranchName() +" revision " +myref.GetHash());
|
|
|
FetchCommit(myref, new OnResponseListener<GitCommit>() {
|
|
|
@Override
|
|
|
- public void onResponse(GitCommit result) {
|
|
|
+ public void onResponse(final GitCommit result) {
|
|
|
response.onMsg("Finished read commit");
|
|
|
response.onMsg(result.GetMessage());
|
|
|
- response.onMsg("Reading tree #" +result.GetTree());
|
|
|
+ response.onMsg("Reading tree #" +GitSha1.BytesToString(result.GetTreeHash()));
|
|
|
FetchTree(result, new OnStreamResponseListener<GitObject.GitTree>() {
|
|
|
@Override
|
|
|
public void onMsg(String message) {
|
|
|
@@ -318,8 +405,8 @@ class HttpGitProtocol implements GitInterface {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public void onResponse(GitObject.GitTree result) {
|
|
|
- response.onMsg("Finished read tree");
|
|
|
+ public void onResponse(GitObject.GitTree tree) {
|
|
|
+ response.onMsg("Finished reading tree");
|
|
|
response.onResponse(result);
|
|
|
}
|
|
|
|
|
|
@@ -359,11 +446,20 @@ class HttpGitProtocol implements GitInterface {
|
|
|
protoInflateGet(url, response);
|
|
|
}
|
|
|
|
|
|
- public void debugTree(String rootGit, GitObject.GitTree tree) {
|
|
|
- log.severe(">>> DEBUG >> Tree hash is " +GitSha1.getSha1OfObject(rootGit, tree));
|
|
|
+ public byte[] makePack(List<GitPackable> objectsToPack) throws IOException
|
|
|
+ {
|
|
|
+ ByteArrayOutputStream msg = new ByteArrayOutputStream();
|
|
|
+ msg.write("PACK".getBytes());
|
|
|
+ msg.write(new byte[] { 0, 0, 0, 2 } );
|
|
|
+ msg.write(ByteBuffer.allocate(4).putInt(objectsToPack.size()).array());
|
|
|
+ for (GitPackable i: objectsToPack) {
|
|
|
+ byte[] pack = i.GetPack();
|
|
|
+ byte[] header = GitPackableUtil.getObjHeader(i.GetPackableType(), 16711680);
|
|
|
+ }
|
|
|
+ return msg.toByteArray();
|
|
|
}
|
|
|
|
|
|
- public void PushBlobs(String rootGit, GitObject.GitTree tree, final Map<String, GitObject.GitBlob> blobs, final File[] files, final GitInterface.OnStreamResponseListener<Void> response) {
|
|
|
+ public void PushBlobs(final GitCommit.Builder commit, final GitInterface.OnStreamResponseListener<Void> response) {
|
|
|
GetRefs(new GitInterface.OnResponseListener<GitRef[]>() {
|
|
|
@Override
|
|
|
public void onResponse(final GitRef[] result) {
|
|
|
@@ -373,7 +469,55 @@ class HttpGitProtocol implements GitInterface {
|
|
|
myref = ref;
|
|
|
}
|
|
|
if (myref != null) {
|
|
|
+ final GitRef finalRef = myref;
|
|
|
response.onMsg("Pushing over " +myref.GetBranchName() +" revision " +myref.GetHash());
|
|
|
+ ByteArrayOutputStream msg = new ByteArrayOutputStream();
|
|
|
+ try {
|
|
|
+ HashMap<String, String> headers = new HashMap<>();
|
|
|
+ headers.put("Content-Type", "application/x-git-receive-pack-request");
|
|
|
+ ProtoPost(new URL(fConfig.GetUrl() +"/git-receive-pack"), headers, new OnResponseListener<byte[]>() {
|
|
|
+ @Override
|
|
|
+ public void onResponse(byte[] result) {
|
|
|
+ Logger.getAnonymousLogger().severe(new String(result, Charset.defaultCharset()));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onError(String msg, Throwable e) {
|
|
|
+ response.onError(msg, e);
|
|
|
+ }
|
|
|
+ }, new OnResponseListener<OutputStream>() {
|
|
|
+ @Override
|
|
|
+ public void onResponse(OutputStream result) {
|
|
|
+ StringBuilder msgBuilder = new StringBuilder();
|
|
|
+ GitCommit ci = commit.Build();
|
|
|
+ byte[] ciRaw = ci.getRawFormat().getBytes();
|
|
|
+ msgBuilder.append(finalRef.GetHash() + " " + GitSha1.BytesToString(GitSha1.getCommitHash(ci)) +" " +finalRef.GetBranchName());
|
|
|
+ byte[] msgLine = msgBuilder.toString().getBytes();
|
|
|
+ try {
|
|
|
+ result.write(String.format(Locale.US, "%04d", msgLine.length).getBytes());
|
|
|
+ result.write(msgLine);
|
|
|
+ result.write("\n0000\n".getBytes());
|
|
|
+
|
|
|
+ result.write(String.format(Locale.US, "%04d", ciRaw.length).getBytes());
|
|
|
+ result.write(ciRaw);
|
|
|
+ result.write(makePack(commit.PreparePack()));
|
|
|
+ }
|
|
|
+ catch (IOException e) {
|
|
|
+ log.log(Level.SEVERE, "Cannot git-upload-pack: " +e.getMessage(), e);
|
|
|
+ onError(e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onError(String msg, Throwable e) {
|
|
|
+ response.onError(msg, e);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ catch (IOException e) {
|
|
|
+ response.onError(e.getMessage(), e);
|
|
|
+ return;
|
|
|
+ }
|
|
|
// FIXME
|
|
|
response.onResponse(null);
|
|
|
} else {
|