Browse Source

[refactor] removed dependencies to cpp bridge
[add] begin of packfile writer

isundil 7 years ago
parent
commit
383c70a7aa

+ 1 - 12
app/build.gradle

@@ -9,11 +9,6 @@ android {
         versionCode 1
         versionName "1.0"
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
-        externalNativeBuild {
-            cmake {
-                cppFlags ""
-            }
-        }
         vectorDrawables.useSupportLibrary = true
     }
     buildTypes {
@@ -22,18 +17,12 @@ android {
             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
         }
     }
-    externalNativeBuild {
-
-        cmake {
-            path "CMakeLists.txt"
-        }
-    }
 }
 
 dependencies {
     implementation fileTree(include: ['*.jar'], dir: 'libs')
     implementation 'com.android.support:appcompat-v7:27.1.1'
-    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
+    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
     implementation 'com.android.support:support-v4:27.1.1'
     implementation 'com.android.support:support-vector-drawable:27.1.1'
     testImplementation 'junit:junit:4.12'

+ 2 - 1
app/src/main/AndroidManifest.xml

@@ -44,6 +44,7 @@
         <activity
             android:name=".ui.EditPasswordActivity"
             android:windowSoftInputMode="adjustResize" />
-        <activity android:name=".ui.GitPullActivity"/>
+        <activity android:name=".ui.GitPullActivity">
+        </activity>
     </application>
 </manifest>

+ 0 - 4
app/src/main/cpp/native-lib.cpp

@@ -1,4 +0,0 @@
-#include <jni.h>
-
-extern "C" JNIEXPORT void JNICALL Java_info_knacki_pass_bridge_ZLib_Inflate(JNIEnv *env, jobject) {
-}

+ 0 - 13
app/src/main/java/info/knacki/pass/bridge/BridgeUtils.java

@@ -1,13 +0,0 @@
-package info.knacki.pass.bridge;
-
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-public class BridgeUtils {
-    private static final Logger log = Logger.getLogger(BridgeUtils.class.getName());
-
-    public static void LoadLibraries() {
-        log.log(Level.INFO, "Load libraries");
-        System.loadLibrary("native-lib");
-    }
-}

+ 0 - 5
app/src/main/java/info/knacki/pass/bridge/ZLib.java

@@ -1,5 +0,0 @@
-package info.knacki.pass.bridge;
-
-public class ZLib {
-    public static native void Inflate();
-}

+ 2 - 6
app/src/main/java/info/knacki/pass/git/GitInterface.java

@@ -24,13 +24,9 @@ public interface GitInterface {
 
     void FetchTree(GitCommit ci, OnStreamResponseListener<GitObject.GitTree> response);
 
-    void FetchTree(OnStreamResponseListener<GitObject.GitTree> response);
+    void FetchHead(final OnStreamResponseListener<GitCommit> response);
 
     void FetchBlob(GitObject.GitBlob blob, OnResponseListener<byte[]> response);
 
-    void PushBlobs(String rootGit, GitObject.GitTree tree, Map<String, GitObject.GitBlob> blobs, File[] files, GitInterface.OnStreamResponseListener<Void> resp);
-
-
-
-    void debugTree(String rootGit, GitObject.GitTree tree); // FIXME
+    void PushBlobs(GitCommit.Builder commitBuilder, GitInterface.OnStreamResponseListener<Void> resp);
 }

+ 60 - 3
app/src/main/java/info/knacki/pass/git/GitSha1.java

@@ -9,10 +9,20 @@ import java.io.InputStream;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 
+import info.knacki.pass.git.entities.GitCommit;
 import info.knacki.pass.git.entities.GitObject;
 
 public class GitSha1 {
-    private static String getHash(byte[] sha1Bytes) {
+    public static byte[] StringToBytes(String hash) {
+        byte[] result = new byte[20];
+        int j =0;
+
+        for (int i =0; i +1 < hash.length(); i += 2)
+            result[j++] = (byte) Integer.parseInt(hash.substring(i, i+2), 16);
+        return result;
+    }
+
+    public static String BytesToString(byte[] sha1Bytes) {
         StringBuilder sb = new StringBuilder();
         for (byte i: sha1Bytes)
             sb.append(Integer.toString((i & 0xff) + 0x100, 16).substring(1));
@@ -22,7 +32,7 @@ public class GitSha1 {
     public static String getSha1OfFile(String rootPath, String filePath)
     {
         byte[] raw = getRawSha1OfFile(new File(rootPath +filePath));
-        return raw.length == 0 ? "" : getHash(raw);
+        return raw.length == 0 ? "" : BytesToString(raw);
     }
 
     public static byte[] getRawSha1OfFile(String rootPath, String filePath)
@@ -74,8 +84,24 @@ public class GitSha1 {
         return str.toByteArray();
     }
 
+    public static byte[] getTreeMsg(GitObject.GitTree tree) {
+        ByteArrayOutputStream str = new ByteArrayOutputStream();
+
+        try {
+            for (GitObject go : tree.GetObjects()) {
+                str.write((go.GetMode() + ' ').getBytes());
+                str.write(go.GetFilename().getBytes());
+                str.write(new byte[]{0});
+                str.write(go.GetHash());
+            }
+        }
+        catch (IOException e) {}
+        return str.toByteArray();
+    }
+
+
     public static String getSha1OfObject(String rootPath, GitObject obj) {
-        return getHash(getRawSha1OfObject(rootPath, obj));
+        return BytesToString(getRawSha1OfObject(rootPath, obj));
     }
 
     private static byte[] getRawSha1OfTree(String rootPath, GitObject.GitTree tree) {
@@ -93,11 +119,42 @@ public class GitSha1 {
         }
     }
 
+    public static byte[] getRawSha1OfTreeNotRecursive(GitObject.GitTree tree) {
+        try {
+            MessageDigest sha1Builder = MessageDigest.getInstance("SHA1");
+            byte[] msg = getTreeMsg(tree);
+            sha1Builder.update(("tree " +msg.length).getBytes());
+            sha1Builder.update(new byte[] { 0 });
+            sha1Builder.update(msg);
+            byte[] digest = sha1Builder.digest();
+            return digest;
+        }
+        catch (NoSuchAlgorithmException e) {
+            return new byte[]{};
+        }
+    }
+
     private static byte[] getRawSha1OfBlob(String rootPath, GitObject.GitBlob blob) {
         return getRawSha1OfFile(rootPath, blob.GetGitPath());
     }
 
     public static byte[] getRawSha1OfObject(String rootPath, GitObject obj) {
+        byte[] hash = obj.GetHash();
+        if (hash != null)
+            return hash;
         return obj instanceof GitObject.GitTree ? getRawSha1OfTree(rootPath, (GitObject.GitTree) obj) : getRawSha1OfBlob(rootPath, (GitObject.GitBlob) obj);
     }
+
+    public static byte[] getCommitHash(GitCommit ci) {
+        try {
+            MessageDigest sha1Builder = MessageDigest.getInstance("SHA1");
+            sha1Builder.update(ci.getRawFormat().getBytes());
+            byte[] digest = sha1Builder.digest();
+            return digest;
+        }
+        catch (NoSuchAlgorithmException e) {
+            return new byte[]{};
+        }
+
+    }
 }

+ 175 - 31
app/src/main/java/info/knacki/pass/git/HttpGitProtocol.java

@@ -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 {

+ 93 - 5
app/src/main/java/info/knacki/pass/git/entities/GitCommit.java

@@ -1,11 +1,23 @@
 package info.knacki.pass.git.entities;
 
-public class GitCommit {
-    private String fTree;
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import info.knacki.pass.git.GitSha1;
+
+public class GitCommit implements GitPackable {
+    private String fHash = null;
+    private byte[] fTreeHash;
+    private GitObject.GitTree fTree;
     private String fParent;
     private String fAuthor;
     private String fCommitter;
-    private final String fMessage;
+    private String fMessage;
 
     public GitCommit(String sha1Content) {
         int pos = sha1Content.indexOf('\0');
@@ -19,7 +31,7 @@ public class GitCommit {
             else if ("".equals(line))
                 message = new StringBuilder();
             else if (line.startsWith("tree"))
-                fTree = Util.RemoveHead(line);
+                fTreeHash = GitSha1.StringToBytes(Util.RemoveHead(line));
             else if (line.startsWith("parent"))
                 fParent = Util.RemoveHead(line);
             else if (line.startsWith("author"))
@@ -28,16 +40,30 @@ public class GitCommit {
                 fCommitter = Util.RemoveHead(line);
         }
         fMessage = message == null ? "" : message.toString();
+        fTree = null;
+    }
+
+    protected GitCommit(GitCommit parent, String author, String committer, String msg) {
+        fAuthor = author;
+        fCommitter = committer;
+        fParent = parent.fHash;
+        fMessage = msg;
     }
 
     public String GetMessage() {
         return fMessage;
     }
 
-    public String GetTree() {
+    public GitObject.GitTree GetTree() {
         return fTree;
     }
 
+    public byte[] GetTreeHash() { return fTreeHash; }
+
+    public GitObject.GitTree InitializeTree() {
+        return fTree = new GitObject.GitTree(GetTreeHash());
+    }
+
     public String GetAuthor() {
         return fAuthor;
     }
@@ -49,4 +75,66 @@ public class GitCommit {
     public String GetParent() {
         return fParent;
     }
+
+    public String getRawFormat() {
+        Date date = new Date();
+        String dateStr = date.getTime() +" " +(new SimpleDateFormat("%Z", Locale.US).format(date));
+        return "tree " +GitSha1.BytesToString(fTree.GetHash()) +"\n" +
+                "parent " +GetParent() +"\n" +
+                "author " +fAuthor +" " +dateStr +"\n" +
+                "committer " +fAuthor +" " +dateStr +"\n" +
+                "\n" +
+                fMessage +"\n";
+    }
+
+    @Override
+    public eType GetPackableType() {
+        return eType.eType_Commit;
+    }
+
+    @Override
+    public byte[] GetPack() {
+        // FIXME
+        return new byte[] {};
+    }
+
+    public static class Builder {
+        private final GitCommit co;
+        private final GitCommit fParent;
+        private final GitObject.GitTree fTree;
+        private final ArrayDeque<File> fFilesToPush = new ArrayDeque<>();
+
+        public Builder(GitCommit parent, String author, String committer, String message) {
+            co = new GitCommit(parent, author, committer, message);
+            fParent = parent;
+            fTree = new GitObject.GitTree(parent.GetTree());
+        }
+
+        public GitCommit Build() {
+            co.fTree = fTree;
+            return co;
+        }
+
+        public GitCommit.Builder AddFile(String relativeFilename, File f) {
+            fFilesToPush.add(f);
+            fTree.AddItem(relativeFilename, f);
+            return this;
+        }
+
+        public GitCommit.Builder RmFile(String relativeFilename) {
+            GitObject obj = fTree.GetObject(relativeFilename);
+            if (obj != null) {
+                GitObject.GitTree parent = obj.GetParent();
+                if (parent != null)
+                    parent.Remove(obj.GetHash());
+            }
+            return this;
+        }
+
+        public List<GitPackable> PreparePack() {
+            ArrayList<GitPackable> pack = new ArrayList<>();
+            pack.add(Build());
+            return pack;
+        }
+    }
 }

+ 105 - 17
app/src/main/java/info/knacki/pass/git/entities/GitObject.java

@@ -2,16 +2,21 @@ package info.knacki.pass.git.entities;
 
 import android.support.annotation.NonNull;
 
+import java.io.File;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.Map;
 import java.util.SortedSet;
 import java.util.TreeSet;
+import java.util.logging.Logger;
 
-public class GitObject implements Comparable<GitObject> {
+import info.knacki.pass.git.GitSha1;
+
+public abstract class GitObject implements Comparable<GitObject>, GitPackable {
     final GitTree fParent;
     final byte[] fMode;
     final String fName;
-    final String fSha1;
-    boolean dirty = false;
+    byte[] fSha1;
 
     @Override
     public int compareTo(@NonNull GitObject gitObject) {
@@ -19,28 +24,61 @@ public class GitObject implements Comparable<GitObject> {
     }
 
     public static class GitBlob extends GitObject {
-        private GitBlob(GitTree parent, byte[] mode, String name, String sha1) {
+        private GitBlob(GitTree parent, byte[] mode, String name, byte[] sha1) {
             super(parent, mode, name, sha1);
         }
+
+        private GitBlob(GitBlob copy) {
+            this(copy.fParent, copy.fMode, copy.fName, copy.fSha1);
+        }
+
+        GitBlob(GitTree parent, String filename, File f) {
+            this(parent, new byte[] { 49, 48, 48, 54, 52, 52 }, filename, GitSha1.getRawSha1OfFile(f));
+            Logger.getAnonymousLogger().severe("Sha1 of new file " +GetGitPath() +": " +GitSha1.BytesToString(fSha1));
+        }
+
+        @Override
+        public eType GetPackableType() {
+            return eType.eType_Blob;
+        }
+
+        @Override
+        public byte[] GetPack() {
+            // FIXME
+            return new byte[] {};
+        }
     }
 
     public static class GitTree extends GitObject {
-        protected HashMap<String, GitObject> fItems = null;
+        HashMap<String, GitObject> fItems = null;
 
-        private GitTree(GitTree parent, byte[] mode, String name, String sha1) {
+        private GitTree(GitTree parent, byte[] mode, String name, byte[] sha1) {
             super(parent, mode, name, sha1);
         }
 
-        public GitTree(String sha1) {
+        GitTree(GitTree copy) {
+            this(copy.fParent, copy.fMode, copy.fName, copy.fSha1);
+            Initialize();
+            for (Map.Entry<String, GitObject> o: copy.fItems.entrySet()) {
+                GitObject oCopy = o.getValue() instanceof GitTree ? new GitTree((GitTree) o.getValue()) : new GitBlob((GitBlob) o.getValue());
+                fItems.put(o.getKey(), oCopy);
+            }
+        }
+
+        GitTree(byte[] sha1) {
             super(null, new byte[] { '4', '0', '0', '0', '0' }, "", sha1);
         }
 
+        private GitTree(GitTree parent, String filename) {
+            super(parent, new byte[] { '4', '0', '0', '0', '0' }, filename, null);
+        }
+
         public GitTree Initialize() {
             fItems = new HashMap<>();
             return this;
         }
 
-        public boolean IsInitialized() {
+        boolean IsInitialized() {
             return null != fItems;
         }
 
@@ -80,8 +118,7 @@ public class GitObject implements Comparable<GitObject> {
         }
 
         public Iterable<GitObject> GetObjects() {
-            SortedSet<GitObject> result = new TreeSet<>(fItems.values());
-            return result;
+            return new TreeSet<>(fItems.values());
         }
 
         public Iterable<GitBlob> GetBlobs() {
@@ -119,23 +156,74 @@ public class GitObject implements Comparable<GitObject> {
             FindAllBlobs(result);
             return result;
         }
+
+        public boolean IsRoot() {
+            return fParent == null;
+        }
+
+        public GitTree AddItem(String path, File f) {
+            if ('/' == path.charAt(0))
+                path = path.substring(1);
+            final int nextSlash = path.indexOf('/');
+            final String filename = nextSlash == -1 ? path : (path.substring(0, nextSlash));
+            final GitObject obj = GetObject(filename);
+
+            if (obj == null) {
+                if (nextSlash != -1) {
+                    GitTree child = new GitTree(this, filename);
+                    child.AddItem(path.substring(nextSlash + 1), f);
+                } else {
+                    fItems.put(filename, new GitBlob(this, filename, f));
+                }
+            } else if (nextSlash == -1) {
+                Remove(obj.fSha1);
+                fItems.put(filename, new GitBlob(this, filename, f));
+            } else {
+                ((GitTree) obj).AddItem(path.substring(nextSlash + 1), f);
+            }
+            fSha1 = GitSha1.getRawSha1OfTreeNotRecursive(this);
+            Logger.getAnonymousLogger().severe("New sha for " +GetGitPath()+ ": " +GitSha1.BytesToString(fSha1));
+            return this;
+        }
+
+        public GitTree Remove(byte[] sha1) {
+            for (Map.Entry<String, GitObject> i: fItems.entrySet()) {
+                if (Arrays.equals(i.getValue().GetHash(), sha1)) {
+                    fItems.remove(i.getKey());
+                    break;
+                }
+            }
+            fSha1 = GitSha1.getRawSha1OfTreeNotRecursive(this);
+            return this;
+        }
+
+        @Override
+        public eType GetPackableType() {
+            return eType.eType_Tree;
+        }
+
+        @Override
+        public byte[] GetPack() {
+            // FIXME
+            return new byte[] {};
+        }
     }
 
-    private GitObject(GitTree parent, byte[] mode, String name, String sha1) {
+    private GitObject(GitTree parent, byte[] mode, String name, byte[] sha1) {
         fParent = parent;
         fMode = mode;
         fName = name;
         fSha1 = sha1;
     }
 
-    public static GitObject factory(GitTree parent, byte[] mode, String name, String sha1) {
+    public static GitObject factory(GitTree parent, byte[] mode, String name, byte[] sha1) {
         if (mode[0] == '4') {
             return new GitTree(parent, mode, name, sha1);
         }
         return new GitBlob(parent, mode, name, sha1);
     }
 
-    public String GetHash() {
+    public byte[] GetHash() {
         return fSha1;
     }
 
@@ -154,11 +242,11 @@ public class GitObject implements Comparable<GitObject> {
         return sb.toString();
     }
 
-    public String GetMode() {
-        return new String(fMode);
+    public GitTree GetParent() {
+        return fParent;
     }
 
-    public boolean IsDirty() {
-        return dirty;
+    public String GetMode() {
+        return new String(fMode);
     }
 }

+ 15 - 0
app/src/main/java/info/knacki/pass/git/entities/GitPackable.java

@@ -0,0 +1,15 @@
+package info.knacki.pass.git.entities;
+
+public interface GitPackable {
+    enum eType {
+        eType_Commit,
+        eType_Tree,
+        eType_Blob,
+        eType_Tag,
+        eType_OfsDelta,
+        eType_RefDelta
+    }
+
+    eType GetPackableType();
+    byte[] GetPack();
+}

+ 60 - 0
app/src/main/java/info/knacki/pass/git/entities/GitPackableUtil.java

@@ -0,0 +1,60 @@
+package info.knacki.pass.git.entities;
+
+import java.io.ByteArrayOutputStream;
+import java.util.logging.Logger;
+
+public class GitPackableUtil {
+    public static byte getObjType(GitPackable.eType type) {
+        switch (type) {
+            case eType_Commit:
+                return 1;
+            case eType_Tree:
+                return 2;
+            case eType_Blob:
+                return 3;
+            case eType_Tag:
+                return 4;
+            case eType_OfsDelta:
+                return 6;
+            case eType_RefDelta:
+                return 7;
+        }
+        return -1;
+    }
+
+    private static int bitToWrite(long len)
+    {
+        int nbBits = 0;
+        while (len > 0)
+        {
+            nbBits++;
+            len = len >> 1;
+        }
+        int remains = (nbBits -4) % 7;
+        if (remains > 0)
+            nbBits += 7 -remains;
+        return nbBits;
+    }
+
+    public static byte[] getObjHeader(GitPackable.eType type, long len) {
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        byte lastByte;
+        lastByte = (byte) ((getObjType(type)) << 4);
+        int nbBitToWrite = bitToWrite(len);
+        nbBitToWrite -= 4;
+        lastByte |= (byte) (len & 0b1111);
+        len = len >> 4;
+        while (nbBitToWrite > 0) {
+            lastByte |= 0b10000000;
+            buffer.write(lastByte);
+            Logger.getAnonymousLogger().severe(Integer.toBinaryString(lastByte & 0xFF).replace(' ', '0'));
+            lastByte = 0;
+            nbBitToWrite -= 7;
+            lastByte |= (byte)(len & 0b1111111);
+            len = len >> 7;
+        }
+        buffer.write(lastByte);
+        Logger.getAnonymousLogger().severe(Integer.toBinaryString(lastByte & 0xFF).replace(' ', '0'));
+        return buffer.toByteArray();
+    }
+}

+ 21 - 2
app/src/main/java/info/knacki/pass/settings/SettingsManager.java

@@ -63,6 +63,8 @@ public class SettingsManager {
         protected String fUser;
         protected String fPassword;
         protected String fBranch;
+        protected String fUsername;
+        protected String fUserEmail;
 
         Git(String data) {
             if (null == data || data.isEmpty()) {
@@ -92,6 +94,12 @@ public class SettingsManager {
                         case "branch":
                             fBranch = reader.nextString();
                             break;
+                        case "username":
+                            fUsername = reader.nextString();
+                            break;
+                        case "email":
+                            fUserEmail = reader.nextString();
+                            break;
                     }
                 }
                 reader.close();
@@ -115,6 +123,8 @@ public class SettingsManager {
             fBranch = "";
             fUser = "";
             fPassword = "";
+            fUsername = "";
+            fUserEmail = "";
         }
 
         public String GetName() {
@@ -147,6 +157,14 @@ public class SettingsManager {
             return fUser;
         }
 
+        public String GetUsername() { return fUsername; }
+
+        public String SetUsername(String val) { return fUsername = val; }
+
+        public String GetUserEmail() { return fUserEmail; }
+
+        public String SetUserEmail(String val) { return fUserEmail = val; }
+
         public String SetBranch(String branch) {
             return fBranch = branch;
         }
@@ -160,7 +178,6 @@ public class SettingsManager {
         }
 
         private static String jsonEscape(String in) {
-            //FIXME
             return in;
         }
 
@@ -169,7 +186,9 @@ public class SettingsManager {
                     "\"url\":\"" +jsonEscape(fUrl) +"\"," +
                     "\"user\":\"" +jsonEscape(fUser) +"\"," +
                     "\"pass\":\"" +jsonEscape(fPassword) +"\"," +
-                    "\"branch\":\"" +jsonEscape(fBranch) +"\"" +
+                    "\"branch\":\"" +jsonEscape(fBranch) +"\"," +
+                    "\"username\":\"" +jsonEscape(fUsername) +"\"," +
+                    "\"email\":\"" +jsonEscape(fUserEmail) +"\"" +
                     "}]";
         }
     }

+ 74 - 72
app/src/main/java/info/knacki/pass/settings/ui/SettingsActivity.java

@@ -14,6 +14,7 @@ import android.preference.PreferenceActivity;
 import android.preference.SwitchPreference;
 import android.support.v7.app.ActionBar;
 import android.preference.PreferenceFragment;
+import android.util.SparseArray;
 import android.view.MenuItem;
 import android.support.v4.app.NavUtils;
 import android.view.View;
@@ -284,6 +285,22 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
                     SettingsManager.SetVCS(getActivity(), git);
                 }
             });
+            findPreference(getResources().getString(R.string.id_vcs_git_ci_username)).setOnPreferenceChangeListener(new PrefListener() {
+                @Override
+                void savePref(Preference preference, Object o) {
+                    SettingsManager.Git git = (SettingsManager.Git) SettingsManager.GetVCS(getActivity());
+                    git.SetUsername(((String) o).trim());
+                    SettingsManager.SetVCS(getActivity(), git);
+                }
+            });
+            findPreference(getResources().getString(R.string.id_vcs_git_ci_useremail)).setOnPreferenceChangeListener(new PrefListener() {
+                @Override
+                void savePref(Preference preference, Object o) {
+                    SettingsManager.Git git = (SettingsManager.Git) SettingsManager.GetVCS(getActivity());
+                    git.SetUserEmail(((String) o).trim());
+                    SettingsManager.SetVCS(getActivity(), git);
+                }
+            });
             findPreference(getResources().getString(R.string.id_vcs_git_branches)).setOnPreferenceChangeListener(new PrefListener() {
                 @Override
                 void savePref(Preference preference, Object o) {
@@ -303,80 +320,75 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
             reload();
         }
 
-        final HashMap<String, Preference> hiddenPreferences = new HashMap<>();
+        final SparseArray<Preference> hiddenPreferences = new SparseArray<>();
+
+        private void RemovePrefs(int... prefIds) {
+            for (int id: prefIds) {
+                Preference pref = findPreference(getResources().getString(id));
+                if (pref != null) {
+                    getPreferenceScreen().removePreference(pref);
+                    hiddenPreferences.put(id, pref);
+                }
+            }
+        }
+
+        private Preference FindPreference(int prefId) {
+            Preference p = findPreference(getResources().getString(prefId));
+            if (p != null)
+                return p;
+            p = hiddenPreferences.get(prefId);
+            hiddenPreferences.remove(prefId);
+            if (p != null)
+                getPreferenceScreen().addPreference(p);
+            return p;
+        }
 
         protected void reload() {
             final SettingsManager.VCS versioning = SettingsManager.GetVCS(getActivity());
 
             if (versioning != null) {
                 ((SwitchPreference) findPreference(getResources().getString(R.string.id_vcs_enable))).setChecked(true);
-                ListPreference vcsType = (ListPreference) findPreference(getResources().getString(R.string.id_vcs_list));
+                ListPreference vcsType = (ListPreference) FindPreference(R.string.id_vcs_list);
                 vcsType.setEnabled(true);
                 if (versioning instanceof SettingsManager.Git) {
                     vcsType.setValueIndex(0);
                     vcsType.setSummary(vcsType.getEntries()[0]);
-                    EditTextPreference gitUrl = (EditTextPreference) findPreference(getResources().getString(R.string.id_vcs_git_url));
-                    if (gitUrl == null) {
-                        gitUrl = (EditTextPreference) hiddenPreferences.remove(getResources().getString(R.string.id_vcs_git_url));
-                        getPreferenceScreen().addPreference(gitUrl);
-                    }
+                    EditTextPreference gitUrl = (EditTextPreference) FindPreference(R.string.id_vcs_git_url);
                     gitUrl.setSummary(((SettingsManager.Git) versioning).GetUrl());
                     gitUrl.setText(((SettingsManager.Git) versioning).GetUrl());
 
-                    EditTextPreference gitUser = (EditTextPreference) findPreference(getResources().getString(R.string.id_vcs_git_user));
-                    if (gitUser == null) {
-                        gitUser = (EditTextPreference) hiddenPreferences.remove(getResources().getString(R.string.id_vcs_git_user));
-                        getPreferenceScreen().addPreference(gitUser);
-                    }
+                    FindPreference(R.string.id_vcs_git_authcategory);
+                    EditTextPreference gitUser = (EditTextPreference) FindPreference(R.string.id_vcs_git_user);
                     gitUser.setSummary(((SettingsManager.Git) versioning).GetUser());
                     gitUser.setText(((SettingsManager.Git) versioning).GetUser());
 
-                    EditTextPreference gitPass = (EditTextPreference) findPreference(getResources().getString(R.string.id_vcs_git_pass));
-                    if (gitPass == null) {
-                        gitPass = (EditTextPreference) hiddenPreferences.remove(getResources().getString(R.string.id_vcs_git_pass));
-                        getPreferenceScreen().addPreference(gitPass);
-                    }
+                    EditTextPreference gitPass = (EditTextPreference) FindPreference(R.string.id_vcs_git_pass);
                     gitPass.setSummary(((SettingsManager.Git) versioning).GetPassword().length() == 0 ? "" : "******");
                     gitPass.setText(((SettingsManager.Git) versioning).GetPassword());
 
-                    ListPreference gitBranches = (ListPreference) findPreference(getResources().getString(R.string.id_vcs_git_branches));
-                    if (gitBranches == null) {
-                        gitBranches = (ListPreference) hiddenPreferences.remove(getResources().getString(R.string.id_vcs_git_branches));
-                        getPreferenceScreen().addPreference(gitBranches);
-                    }
+                    FindPreference(R.string.id_vcs_git_commitinfocategory);
+                    ListPreference gitBranches = (ListPreference) FindPreference(R.string.id_vcs_git_branches);
                     populateBranches(gitBranches, (SettingsManager.Git) versioning);
 
-                    Preference gitPull = findPreference(getResources().getString(R.string.id_vcs_git_pull));
-                    if (gitPull == null) {
-                        gitPull = hiddenPreferences.remove(getResources().getString(R.string.id_vcs_git_pull));
-                        getPreferenceScreen().addPreference(gitPull);
-                    }
+                    FindPreference(R.string.id_vcs_git_pull);
+
+                    EditTextPreference gitUsername = (EditTextPreference) FindPreference(R.string.id_vcs_git_ci_username);
+                    gitUsername.setSummary(((SettingsManager.Git) versioning).GetUsername());
+                    gitUsername.setDefaultValue(((SettingsManager.Git) versioning).GetUsername());
+
+                    EditTextPreference gitUserEmail = (EditTextPreference) FindPreference(R.string.id_vcs_git_ci_useremail);
+                    gitUserEmail.setSummary(((SettingsManager.Git) versioning).GetUserEmail());
+                    gitUserEmail.setDefaultValue(((SettingsManager.Git) versioning).GetUserEmail());
                 } else {
-                    Preference pref = findPreference(getResources().getString(R.string.id_vcs_git_url));
-                    if (pref != null) {
-                        getPreferenceScreen().removePreference(pref);
-                        hiddenPreferences.put(getResources().getString(R.string.id_vcs_git_url), pref);
-                    }
-                    pref = findPreference(getResources().getString(R.string.id_vcs_git_user));
-                    if (pref != null) {
-                        getPreferenceScreen().removePreference(pref);
-                        hiddenPreferences.put(getResources().getString(R.string.id_vcs_git_user), pref);
-                    }
-                    pref = findPreference(getResources().getString(R.string.id_vcs_git_pass));
-                    if (pref != null) {
-                        getPreferenceScreen().removePreference(pref);
-                        hiddenPreferences.put(getResources().getString(R.string.id_vcs_git_pass), pref);
-                    }
-                    pref = findPreference(getResources().getString(R.string.id_vcs_git_branches));
-                    if (pref != null) {
-                        getPreferenceScreen().removePreference(pref);
-                        hiddenPreferences.put(getResources().getString(R.string.id_vcs_git_branches), pref);
-                    }
-                    pref = findPreference(getResources().getString(R.string.id_vcs_git_pull));
-                    if (pref != null) {
-                        getPreferenceScreen().removePreference(pref);
-                        hiddenPreferences.put(getResources().getString(R.string.id_vcs_git_pull), pref);
-                    }
+                    RemovePrefs(R.string.id_vcs_git_url,
+                            R.string.id_vcs_git_user,
+                            R.string.id_vcs_git_pass,
+                            R.string.id_vcs_git_branches,
+                            R.string.id_vcs_git_pull,
+                            R.string.id_vcs_git_ci_username,
+                            R.string.id_vcs_git_ci_useremail,
+                            R.string.id_vcs_git_commitinfocategory,
+                            R.string.id_vcs_git_authcategory);
                 }
             } else {
                 ((SwitchPreference) findPreference(getResources().getString(R.string.id_vcs_enable))).setChecked(false);
@@ -384,25 +396,15 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
                 vcsType.setEnabled(false);
                 vcsType.setSummary("");
 
-                Preference pref = findPreference(getResources().getString(R.string.id_vcs_git_url));
-                getPreferenceScreen().removePreference(pref);
-                hiddenPreferences.put(getResources().getString(R.string.id_vcs_git_url), pref);
-
-                pref = findPreference(getResources().getString(R.string.id_vcs_git_user));
-                getPreferenceScreen().removePreference(pref);
-                hiddenPreferences.put(getResources().getString(R.string.id_vcs_git_user), pref);
-
-                pref = findPreference(getResources().getString(R.string.id_vcs_git_pass));
-                getPreferenceScreen().removePreference(pref);
-                hiddenPreferences.put(getResources().getString(R.string.id_vcs_git_pass), pref);
-
-                pref = findPreference(getResources().getString(R.string.id_vcs_git_branches));
-                getPreferenceScreen().removePreference(pref);
-                hiddenPreferences.put(getResources().getString(R.string.id_vcs_git_branches), pref);
-
-                pref = findPreference(getResources().getString(R.string.id_vcs_git_pull));
-                getPreferenceScreen().removePreference(pref);
-                hiddenPreferences.put(getResources().getString(R.string.id_vcs_git_pull), pref);
+                RemovePrefs(R.string.id_vcs_git_url,
+                    R.string.id_vcs_git_user,
+                    R.string.id_vcs_git_pass,
+                    R.string.id_vcs_git_branches,
+                    R.string.id_vcs_git_pull,
+                    R.string.id_vcs_git_ci_username,
+                    R.string.id_vcs_git_ci_useremail,
+                    R.string.id_vcs_git_commitinfocategory,
+                    R.string.id_vcs_git_authcategory);
             }
         }
 
@@ -491,7 +493,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
             if (requestCode == ACTIVITY_REQUEST_CODE_BROWSEGPG) {
                 if (resultCode == RESULT_OK) {
                     String path = data.getData().getPath();
-                    Logger.getAnonymousLogger().severe("Coucou path {" +path +"}");
+                    Logger.getAnonymousLogger().severe("Coucou path {" +path +"}"); //FIXME
                     SettingsManager.SetGPGKeyFile(getActivity(), path);
                 }
             }

+ 39 - 16
app/src/main/java/info/knacki/pass/ui/GitPullActivity.java

@@ -2,7 +2,6 @@ package info.knacki.pass.ui;
 
 import android.os.Bundle;
 import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.AppCompatTextView;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
@@ -11,7 +10,9 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 import java.util.Stack;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -21,8 +22,8 @@ import info.knacki.pass.git.GitInterface;
 import info.knacki.pass.git.GitInterfaceFactory;
 import info.knacki.pass.git.GitLocal;
 import info.knacki.pass.git.GitSha1;
+import info.knacki.pass.git.entities.GitCommit;
 import info.knacki.pass.git.entities.GitObject;
-import info.knacki.pass.git.entities.GitRef;
 import info.knacki.pass.io.PathUtils;
 import info.knacki.pass.settings.SettingsManager;
 
@@ -30,7 +31,7 @@ public class GitPullActivity extends AppCompatActivity {
     private final static Logger log = Logger.getLogger(GitPullActivity.class.getName());
 
     private GitInterface fGitInterfage;
-    private GitObject.GitTree fTree;
+    private GitCommit fHeadCommit;
 
     private void onMsg(final String msg) {
         GitPullActivity.this.runOnUiThread(new Runnable() {
@@ -57,15 +58,15 @@ public class GitPullActivity extends AppCompatActivity {
         LOCALGIT_HASH_VERSION_FILE = PathUtils.GetGitFile(this);
         fGitInterfage = GitInterfaceFactory.factory(this, (SettingsManager.Git) versioning);
         if (fGitInterfage != null) {
-            fGitInterfage.FetchTree(new GitInterface.OnStreamResponseListener<GitObject.GitTree>() {
+            fGitInterfage.FetchHead(new GitInterface.OnStreamResponseListener<GitCommit>() {
                 @Override
                 public void onMsg(final String msg) {
                     GitPullActivity.this.onMsg(msg);
                 }
 
                 @Override
-                public void onResponse(GitObject.GitTree result) {
-                    fTree = result;
+                public void onResponse(GitCommit result) {
+                    fHeadCommit = result;
                     GitPullActivity.this.runOnUiThread(new Runnable() {
                         @Override
                         public void run() {
@@ -97,16 +98,16 @@ public class GitPullActivity extends AppCompatActivity {
     protected void OnTreeStructureFetched() {
         final GitLocal localVersion = new GitLocal(new File(LOCALGIT_HASH_VERSION_FILE));
         HashMap<String, GitObject.GitBlob> filesToPull = new HashMap<>();
-        HashMap<String, GitObject.GitBlob> filesToPush = new HashMap<>();
+        Set<String> filesToPush = new HashSet<>();
         HashMap<String, GitObject.GitBlob> conflictingFiles = new HashMap<>();
 
-        for (Map.Entry<String, GitObject.GitBlob>i: fTree.FindAllBlobs().entrySet()) {
+        for (Map.Entry<String, GitObject.GitBlob>i: fHeadCommit.GetTree().FindAllBlobs().entrySet()) {
             String remoteLocalHash = localVersion.GetHash(i.getKey());
             String currentHash = GitSha1.getSha1OfFile(PathUtils.GetPassDir(this), i.getKey());
 
             if (remoteLocalHash == null) remoteLocalHash = "";
 
-            final boolean remoteChanged = !remoteLocalHash.equals(i.getValue().GetHash());
+            final boolean remoteChanged = !remoteLocalHash.equals(GitSha1.BytesToString(i.getValue().GetHash()));
             final boolean localChanged = !remoteLocalHash.equals(currentHash);
 
             if (remoteChanged && localChanged) {
@@ -117,11 +118,11 @@ public class GitPullActivity extends AppCompatActivity {
                 filesToPull.put(i.getKey(), i.getValue());
             } else if (localChanged) {
                 // local changed
-                filesToPush.put(i.getKey(), i.getValue());
+                filesToPush.add(i.getKey());
             }
         }
         for (String i: localVersion.FileNames()) {
-            if (fTree.GetObjectFullPath(i) == null) {
+            if (fHeadCommit.GetTree().GetObjectFullPath(i) == null) {
                 log.severe("Cannot find obj on remote tree " +i);
                 final boolean localChanged = !GitSha1.getSha1OfFile(PathUtils.GetPassDir(this), i).equals(localVersion.GetHash(i));
                 if (localChanged)
@@ -135,7 +136,7 @@ public class GitPullActivity extends AppCompatActivity {
         AskForConflicts(localVersion, conflictingFiles, filesToPull, filesToPush);
     }
 
-    void AskForConflicts(GitLocal localVersion, HashMap<String, GitObject.GitBlob> conflicts, HashMap<String, GitObject.GitBlob> filesToPull, HashMap<String, GitObject.GitBlob> filesToPush) {
+    void AskForConflicts(GitLocal localVersion, HashMap<String, GitObject.GitBlob> conflicts, HashMap<String, GitObject.GitBlob> filesToPull, Set<String> filesToPush) {
         // FIXME
         filesToPull.putAll(conflicts);
         SyncFiles(localVersion, filesToPull, filesToPush);
@@ -154,7 +155,7 @@ public class GitPullActivity extends AppCompatActivity {
                 RmEmptyDirs(i, false);
     }
 
-    void SyncFiles(final GitLocal localVersion, final HashMap<String, GitObject.GitBlob> filesToPull, final HashMap<String, GitObject.GitBlob> filesToPush) {
+    void SyncFiles(final GitLocal localVersion, final HashMap<String, GitObject.GitBlob> filesToPull, final Set<String> filesToPush) {
         final GitInterface.OnStreamResponseListener<Void> allDone = new GitInterface.OnStreamResponseListener<Void>() {
             @Override
             public void onResponse(Void result) {
@@ -167,7 +168,6 @@ public class GitPullActivity extends AppCompatActivity {
                         pg.setProgress(1);
                         localVersion.Write(new File(LOCALGIT_HASH_VERSION_FILE));
                         RmEmptyDirs(new File(PathUtils.GetPassDir(GitPullActivity.this)), true);
-                        fGitInterfage.debugTree(PathUtils.GetPassDir(GitPullActivity.this) +"/", fTree);
                     }
                 });
             }
@@ -292,10 +292,32 @@ public class GitPullActivity extends AppCompatActivity {
             log.log(Level.SEVERE, e.getMessage(), e);
             return;
         }
-        localVersion.SetHash(filename, blob.GetHash());
+        localVersion.SetHash(filename, GitSha1.BytesToString(blob.GetHash()));
     }
 
-    void PushBlobs(final Map<String, GitObject.GitBlob> blobs, final GitInterface.OnStreamResponseListener<Void> resp) {
+    void PushBlobs(final Set<String> files, final GitInterface.OnStreamResponseListener<Void> resp) {
+        SettingsManager.Git config = (SettingsManager.Git) SettingsManager.GetVCS(this);
+        GitCommit.Builder commit = new GitCommit.Builder(fHeadCommit, config.GetUsername(), config.GetUserEmail(), "Android pass sync");
+        for (String i: files)
+            commit.AddFile(i, new File(PathUtils.GetPassDir(this) +i));
+        fGitInterfage.PushBlobs(commit, new GitInterface.OnStreamResponseListener<Void>() {
+            @Override
+            public void onMsg(String message) {
+                Logger.getAnonymousLogger().severe(message);
+            }
+
+            @Override
+            public void onResponse(Void result) {
+                Logger.getAnonymousLogger().severe("DONE");
+            }
+
+            @Override
+            public void onError(String msg, Throwable e) {
+                Logger.getAnonymousLogger().log(Level.SEVERE, msg, e);
+            }
+        });
+        //resp.onResponse(null);
+        /*
         GitPullActivity.this.runOnUiThread(new Runnable() {
             @Override
             public void run() {
@@ -309,5 +331,6 @@ public class GitPullActivity extends AppCompatActivity {
                 fGitInterfage.PushBlobs(PathUtils.GetPassDir(GitPullActivity.this), fTree, blobs, files, resp);
             }
         });
+        */
     }
 }

+ 3 - 11
app/src/main/java/info/knacki/pass/ui/MainActivity.java

@@ -4,8 +4,8 @@ import android.Manifest;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Build;
-import android.support.v7.app.AppCompatActivity;
 import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
@@ -13,19 +13,15 @@ import android.widget.ScrollView;
 import android.widget.Toast;
 
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.nio.channels.FileChannel;
-import java.nio.file.Files;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import info.knacki.pass.R;
-import info.knacki.pass.bridge.BridgeUtils;
 import info.knacki.pass.generator.PasswordGenerator;
 import info.knacki.pass.generator.ui.PasswordGeneratorWizard;
 import info.knacki.pass.git.GitInterface;
+import info.knacki.pass.git.HttpGitProtocol;
 import info.knacki.pass.io.FileInterfaceFactory;
 import info.knacki.pass.io.IFileInterface;
 import info.knacki.pass.io.PathUtils;
@@ -115,10 +111,6 @@ public class MainActivity extends AppCompatActivity implements PasswordClickList
                 .show();
     }
 
-    static {
-        BridgeUtils.LoadLibraries();
-    }
-
     void requestPermissions() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             requestPermissions(new String[] {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
@@ -142,7 +134,7 @@ public class MainActivity extends AppCompatActivity implements PasswordClickList
 
         requestPermissions();
 
-        startActivity(new Intent(this, GitPullActivity.class));
+        //startActivity(new Intent(this, GitPullActivity.class));
     }
 
     @Override

+ 4 - 0
app/src/main/res/values-fr/lang.xml

@@ -28,6 +28,10 @@
     <string name="pref_vcs_git_pass_title">Mot de passe</string>
     <string name="pref_vcs_branch_title">Branche GIT</string>
     <string name="pref_vcs_git_pull">Git pull</string>
+    <string name="pref_vcs_git_authcategory">Authentication</string>
+    <string name="pref_vcs_git_commitinfocategory">Informations de commit</string>
+    <string name="pref_vcs_git_username_title">Nom d\'utilisateur</string>
+    <string name="pref_vcs_git_useremail_title">Adresse email</string>
     <string name="pref_header_GPG">Configuration de GPG</string>
     <array name="pref_enctype_title">
         <item>Pas de chiffrement</item>

+ 4 - 0
app/src/main/res/values/lang.xml

@@ -37,6 +37,10 @@
     <string name="pref_vcs_git_pass_title">Password</string>
     <string name="pref_vcs_branch_title">Git branch</string>
     <string name="pref_vcs_git_pull">Git pull</string>
+    <string name="pref_vcs_git_authcategory">Authentication</string>
+    <string name="pref_vcs_git_commitinfocategory">Commit information</string>
+    <string name="pref_vcs_git_username_title">Username</string>
+    <string name="pref_vcs_git_useremail_title">Mail address</string>
     <string name="pref_header_GPG">GPG settings</string>
     <array name="pref_vcs_list_keys">
         <item>GIT</item>

+ 4 - 0
app/src/main/res/values/strings.xml

@@ -18,6 +18,10 @@
     <string name="id_vcs_git_pass">id_vcs_git_pass</string>
     <string name="id_vcs_git_branches">id_vcs_git_branches</string>
     <string name="id_vcs_git_pull">id_vcs_git_pull</string>
+    <string name="id_vcs_git_ci_username">id_vcs_git_ci_username</string>
+    <string name="id_vcs_git_ci_useremail">id_vcs_git_ci_useremail</string>
+    <string name="id_vcs_git_commitinfocategory">id_vcs_git_commitinfocategory</string>
+    <string name="id_vcs_git_authcategory">id_vcs_git_authcategory</string>
     <string name="id_gpg_keyfile">id_gpg_keyfile</string>
     <string name="id_removeall">id_removeall</string>
 </resources>

+ 12 - 0
app/src/main/res/xml/pref_vcs.xml

@@ -18,6 +18,9 @@
         android:title="@string/pref_vcs_url"
         />
 
+    <PreferenceCategory
+        android:title="@string/pref_vcs_git_authcategory"
+        android:key="@string/id_vcs_git_authcategory"/>
     <EditTextPreference
         android:key="@string/id_vcs_git_user"
         android:title="@string/pref_vcs_git_user_title"/>
@@ -26,10 +29,19 @@
         android:title="@string/pref_vcs_git_pass_title"
         android:inputType="textPassword"/>
 
+    <PreferenceCategory
+        android:title="@string/pref_vcs_git_commitinfocategory"
+        android:key="@string/id_vcs_git_commitinfocategory"/>
     <ListPreference
         android:key="@string/id_vcs_git_branches"
         android:title="@string/pref_vcs_branch_title"
         />
+    <EditTextPreference
+        android:key="@string/id_vcs_git_ci_username"
+        android:title="@string/pref_vcs_git_username_title"/>
+    <EditTextPreference
+        android:key="@string/id_vcs_git_ci_useremail"
+        android:title="@string/pref_vcs_git_useremail_title"/>
 
     <Preference android:title="@string/pref_vcs_git_pull" android:key="@string/id_vcs_git_pull" />
 </PreferenceScreen>