Browse Source

[tmp] git VCS

isundil 7 years ago
parent
commit
3b4f40c818

+ 1 - 1
.idea/misc.xml

@@ -25,7 +25,7 @@
       </value>
     </option>
   </component>
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/build/classes" />
   </component>
   <component name="ProjectType">

+ 2 - 2
app/CMakeLists.txt

@@ -27,7 +27,7 @@ add_library( # Sets the name of the library.
 
 find_library( # Sets the name of the path variable.
               log-lib
-
+              Z
               # Specifies the name of the NDK library that
               # you want CMake to locate.
               log )
@@ -38,7 +38,7 @@ find_library( # Sets the name of the path variable.
 
 target_link_libraries( # Specifies the target library.
                        native-lib
-
+                       z
                        # Links the target library to the log library
                        # included in the NDK.
                        ${log-lib} )

+ 3 - 1
app/build.gradle

@@ -23,6 +23,7 @@ android {
         }
     }
     externalNativeBuild {
+
         cmake {
             path "CMakeLists.txt"
         }
@@ -30,7 +31,7 @@ android {
 }
 
 dependencies {
-    implementation fileTree(dir: 'libs', include: ['*.jar'])
+    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:support-v4:27.1.1'
@@ -38,4 +39,5 @@ dependencies {
     testImplementation 'junit:junit:4.12'
     androidTestImplementation 'com.android.support.test:runner:1.0.2'
     androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+    implementation 'commons-http:commons-http:1.1'
 }

+ 12 - 13
app/src/main/AndroidManifest.xml

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="info.knacki.pass">
-
+    <uses-permission android:name="android.permission.INTERNET"/>
     <application
         android:allowBackup="true"
         android:icon="@mipmap/ic_launcher"
@@ -9,17 +9,6 @@
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
         android:theme="@style/AppTheme">
-        <activity android:name=".ui.MainActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-        <activity
-            android:name=".ui.EditPasswordActivity"
-            android:windowSoftInputMode="adjustResize" />
-
         <service
             android:name=".input.InputService"
             android:permission="android.permission.BIND_INPUT_METHOD">
@@ -40,6 +29,16 @@
                 android:name="android.support.PARENT_ACTIVITY"
                 android:value="info.knacki.pass.ui.MainActivity" />
         </activity>
-    </application>
+        <activity android:name=".ui.MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
 
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".ui.EditPasswordActivity"
+            android:windowSoftInputMode="adjustResize" />
+        <activity android:name=".git.ui.GitPullActivity"/>
+    </application>
 </manifest>

+ 135 - 8
app/src/main/cpp/native-lib.cpp

@@ -1,12 +1,139 @@
 #include <jni.h>
-#include <string>
+#include <stdlib.h>
+#include <string.h>
+#include <deque>
+#include <zlib.h>
 
-extern "C" JNIEXPORT jstring
+#define CHUNK 1024
 
-JNICALL
-Java_info_knacki_pass_passListActivity_stringFromJNI(
-        JNIEnv *env,
-        jobject /* this */) {
-    std::string hello = "Hello from C++";
-    return env->NewStringUTF(hello.c_str());
+/*
+int deflate(char* str_src, char* str_dest, int level)
+{
+    FILE *source;
+    FILE *dest;
+    int ret, flush;
+    unsigned have;
+    z_stream strm;
+    char in[CHUNK];
+    char out[CHUNK];
+
+    source = fopen(str_src, "rb");
+    dest   = fopen(str_dest, "wb");
+
+    strm.zalloc = Z_NULL;
+    strm.zfree  = Z_NULL;
+    strm.opaque = Z_NULL;
+
+    ret = deflateInit(&strm, level);
+
+    if (ret != Z_OK)
+        return ret;
+
+    / * compress until end of file * /
+
+    do
+    {
+        strm.avail_in = fread(in, 1, CHUNK, source);
+
+        if (ferror(source))
+        {
+            deflateEnd(&strm);
+            fclose(source);
+            fclose(dest);
+            return Z_ERRNO;
+        }
+
+        flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;
+
+        strm.next_in = (Bytef*)in;
+
+        do
+        {
+            strm.avail_out = CHUNK;
+            strm.next_out  = (Bytef*) out;
+
+            ret = deflate(&strm, flush);    / * no bad return value * /
+
+            have = CHUNK - strm.avail_out;
+
+            if (fwrite(out, 1, have, dest) != have || ferror(dest))
+            {
+                deflateEnd(&strm);
+                fclose(source);
+                fclose(dest);
+                return Z_ERRNO;
+            }
+        }
+        while (strm.avail_out == 0);
+
+        / * done when last data in file processed * /
+    }
+    while (flush != Z_FINISH);
+
+    / * clean up and return * /
+
+    deflateEnd(&strm);
+
+    fclose(source);
+    fclose(dest);
+
+    return Z_OK;
+}
+*/
+
+jcharArray createCharArray(JNIEnv *env, std::deque<char> deque) {
+    jcharArray result = env->NewCharArray(deque.size());
+    jchar *arr = (jchar *)malloc(sizeof(*arr) * deque.size());
+    for (int i =0; i < deque.size(); ++i)
+        arr[i] = (jchar) deque.at(i);
+    env->SetCharArrayRegion(result, 0, deque.size(), arr);
+    free(arr);
+    return result;
+}
+
+jcharArray inflate(JNIEnv *env, char* in_buf, int bufLen)
+{
+    z_stream strm;
+    char out[CHUNK];
+    std::deque<char> ss;
+
+    strm.zalloc   = Z_NULL;
+    strm.zfree    = Z_NULL;
+    strm.opaque   = Z_NULL;
+    strm.next_in  = Z_NULL;
+    strm.avail_in = 0;
+
+    if (inflateInit2(&strm, -MAX_WBITS) != Z_OK)
+        return nullptr;
+
+    strm.avail_in = (unsigned) bufLen;
+    strm.next_in = (Bytef*) in_buf;
+    do
+    {
+        strm.avail_out = CHUNK;
+        strm.next_out  = (Bytef*) out;
+
+        int ret = inflate(&strm, Z_NO_FLUSH);
+        const unsigned have = CHUNK - strm.avail_out;
+        for (int i =0; i < have; ++i)
+            ss.emplace_back(out[i]);
+        if (ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
+            break;
+    }
+    while (strm.avail_out == 0);
+
+    inflateEnd(&strm);
+    return createCharArray(env, ss);
+}
+
+extern "C" JNIEXPORT jcharArray JNICALL Java_info_knacki_pass_bridge_ZLib_Inflate(JNIEnv *env, jobject, jcharArray input, jint inputLength) {
+    jchar *inputJchar = (jchar*) malloc(sizeof(*inputJchar) *((int)inputLength));
+    char *inputNative = (char*) malloc(sizeof(*inputNative) *((int)inputLength));
+    env->GetCharArrayRegion(input, 0, inputLength, inputJchar);
+    for (int i =0; i < inputLength; ++i)
+        inputNative[i] = (char) inputJchar[i];
+    free(inputJchar);
+    jcharArray  resultJchararray = inflate(env, inputNative, inputLength);
+    free(inputNative);
+    return resultJchararray;
 }

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

@@ -0,0 +1,13 @@
+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");
+    }
+}

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

@@ -0,0 +1,5 @@
+package info.knacki.pass.bridge;
+
+public class ZLib {
+    public static native char[] Inflate(char []str, int length);
+}

+ 296 - 0
app/src/main/java/info/knacki/pass/git/DumbGitInterface.java

@@ -0,0 +1,296 @@
+package info.knacki.pass.git;
+
+import android.arch.core.util.Function;
+import android.os.AsyncTask;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.net.Authenticator;
+import java.net.MalformedURLException;
+import java.net.PasswordAuthentication;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.Charset;
+import java.security.InvalidKeyException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.InvalidPropertiesFormatException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+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.GitRef;
+import info.knacki.pass.git.entities.Util;
+import info.knacki.pass.settings.SettingsManager;
+
+class DumbGitInterface implements GitInterface {
+    protected final SettingsManager.Git fConfig;
+    protected GitRef[] fRefsCache = null;
+    private static final Logger log = Logger.getLogger(DumbGitInterface.class.getName());
+
+    DumbGitInterface(SettingsManager.Git config) {
+        fConfig = config;
+    }
+
+    void protoInflateGet(final URL url, final OnResponseListener<byte[]> callback) {
+        AsyncTask<Void, Void, Integer> task = new AsyncTask<Void, Void, Integer>() {
+            @Override
+            protected Integer doInBackground(Void... voids) {
+                try {
+                    log.log(Level.INFO, "fetching " +url.toString());
+                    URLConnection httpClient = url.openConnection();
+                    if (fConfig.HasAuthentification()) {
+                        Authenticator.setDefault(new Authenticator() {
+                            @Override
+                            protected PasswordAuthentication getPasswordAuthentication() {
+                                return new PasswordAuthentication(fConfig.GetUser(), fConfig.GetPassword().toCharArray());
+                            }
+                        });
+                    }
+                    InflaterInputStream in = new InflaterInputStream(httpClient.getInputStream());
+                    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;
+                    }
+                    callback.onResponse(buffer);
+                }
+                catch (MalformedURLException e) {
+                    log.log(Level.WARNING, e.getMessage(), e);
+                    callback.onError(e.getClass().getSimpleName() +": " +e.getMessage(), e);
+                }
+                catch (IOException e) {
+                    log.log(Level.WARNING, e.getMessage(), e);
+                    callback.onError(e.getClass().getSimpleName() +": " +e.getMessage(), e);
+                }
+                return 0;
+            }
+        };
+        task.execute();
+    }
+
+    void protoGet(final URL url, final OnResponseListener<String> callback) {
+        AsyncTask<Void, Void, Integer> task = new AsyncTask<Void, Void, Integer>() {
+            @Override
+            protected Integer doInBackground(Void... voids) {
+                try {
+                    log.log(Level.INFO, "fetching " +url.toString());
+                    URLConnection httpClient = url.openConnection();
+                    if (fConfig.HasAuthentification()) {
+                        Authenticator.setDefault(new Authenticator() {
+                            @Override
+                            protected PasswordAuthentication getPasswordAuthentication() {
+                                return new PasswordAuthentication(fConfig.GetUser(), fConfig.GetPassword().toCharArray());
+                            }
+                        });
+                    }
+                    BufferedReader in = new BufferedReader(new InputStreamReader(httpClient.getInputStream()));
+                    StringBuilder sb = new StringBuilder();
+                    String buf;
+                    while((buf = in.readLine()) != null)
+                    {
+                        if (sb.length() != 0)
+                            sb.append("\n");
+                        sb.append(buf);
+                    }
+                    in.close();
+                    callback.onResponse(sb.toString());
+                }
+                catch (MalformedURLException e) {
+                    log.log(Level.WARNING, e.getMessage(), e);
+                    callback.onError(e.getClass().getSimpleName() +": " +e.getMessage(), e);
+                }
+                catch (IOException e) {
+                    log.log(Level.WARNING, e.getMessage(), e);
+                    callback.onError(e.getClass().getSimpleName() +": " +e.getMessage(), e);
+                }
+                return 0;
+            }
+        };
+        task.execute();
+    }
+
+    public void GetRefs(final OnResponseListener<GitRef[]> callback) {
+        if (fRefsCache != null) {
+            callback.onResponse(fRefsCache);
+            return;
+        }
+        try {
+            URL url = new URL(fConfig.GetUrl() + "/info/REFS");
+            protoGet(url, new OnResponseListener<String>() {
+                @Override
+                public void onResponse(String result) {
+                    String [] refStrings = result.split("\n");
+                    fRefsCache = new GitRef[refStrings.length];
+                    for (int i =0; i < refStrings.length; ++i) {
+                        String refLine = refStrings[i];
+                        for (int j =0; j < refLine.length(); ++j) {
+                            if (refLine.charAt(j) == '\t') {
+                                fRefsCache[i] = new GitRef(refLine.substring(0, j).trim(), refLine.substring(j +1).trim());
+                                break;
+                            }
+                        }
+                    }
+                    callback.onResponse(fRefsCache);
+                }
+
+                @Override
+                public void onError(String msg, Throwable e) {
+                    callback.onError(msg, e);
+                }
+            });
+        } catch (MalformedURLException e) {
+            callback.onError(e.getMessage(), e);
+        }
+    }
+
+    public void FetchCommit(GitRef ref, final OnResponseListener<GitCommit> response) {
+        PullHash(ref.GetHash(), new OnResponseListener<byte[]>() {
+            @Override
+            public void onResponse(byte[] result) {
+                response.onResponse(new GitCommit(new String(result, Charset.defaultCharset())));
+            }
+
+            @Override
+            public void onError(String msg, Throwable e) {
+                response.onError(msg, e);
+            }
+        });
+    }
+
+    // FIXME recursive strategy
+
+    public void RecursiveFetchTree(final GitObject.GitTree rootTree, final OnResponseListener<GitObject.GitTree> response) {
+        response.onResponse(rootTree);
+    }
+
+    private void FillTree(final GitObject.GitTree tree, byte[] data) {
+        int i = 3;
+
+        while (data[i -1] != 0 && i < data.length)
+            ++i;
+        while (i < data.length) {
+            byte[] mode = new byte[6];
+            System.arraycopy(data, i, mode, 0, 6);
+            i += 6;
+            int len = 0;
+            while (i +len < data.length && data[i +len] != 0)
+                ++len;
+            byte[] filenameBytes = new byte[len];
+            System.arraycopy(data, i, filenameBytes, 0, len);
+            i += len +1;
+            String fileName = new String(filenameBytes, Charset.defaultCharset());
+            byte []sha1 = new byte[20];
+            System.arraycopy(data, i, sha1, 0, 20);
+            i += 20;
+            tree.AddItem(GitObject.factory(new String(mode, Charset.defaultCharset()).toCharArray(), fileName, sha1));
+        }
+    }
+
+    public void FetchTree(final GitCommit ci, final OnResponseListener<GitObject.GitTree> response) {
+        PullHash(ci.GetTree(), new OnResponseListener<byte[]>() {
+            @Override
+            public void onResponse(byte[] result) {
+                GitObject.GitTree tree = new GitObject.GitTree(ci.GetTree()).Initialize();
+                FillTree(tree, result);
+                RecursiveFetchTree(tree, response);
+            }
+
+            @Override
+            public void onError(String msg, Throwable e) {
+                response.onError(msg, e);
+            }
+       });
+    }
+
+    public void Pull(final OnStreamResponseListener<Void> response) {
+        GetRefs(new GitInterface.OnResponseListener<GitRef[]>() {
+            @Override
+            public void onResponse(final GitRef[] result) {
+                GitRef myref = null;
+                response.onMsg("Found refs: ");
+                for (GitRef ref: result) {
+                    response.onMsg("\t> "+ref.GetBranch());
+                    if (ref.GetBranch().equals(fConfig.GetBranch()))
+                        myref = ref;
+                }
+                if (myref != null) {
+                    response.onMsg("Checking out branch " +myref.GetBranchName() +" revision " +myref.GetHash());
+                    FetchCommit(myref, new OnResponseListener<GitCommit>() {
+                        @Override
+                        public void onResponse(GitCommit result) {
+                            response.onMsg("Finished read commit");
+                            response.onMsg(result.GetMessage());
+                            response.onMsg("Reading tree #" +result.GetTree());
+                            FetchTree(result, new OnResponseListener<GitObject.GitTree>() {
+                                @Override
+                                public void onResponse(GitObject.GitTree result) {
+                                    response.onMsg("Finished read tree");
+                                }
+
+                                @Override
+                                public void onError(String msg, Throwable e) {
+                                    response.onError(msg, e);
+                                }
+                            });
+                        }
+
+                        @Override
+                        public void onError(String msg, Throwable e) {
+                            response.onError(msg, e);
+                        }
+                    });
+                } else {
+                    response.onError("Branch " +fConfig.GetBranch() + " not found on remote", null);
+                }
+            }
+
+            @Override
+            public void onError(final String msg, final Throwable e) {
+                response.onError(msg, e);
+            }
+        });
+    }
+
+    public void PullHash(String hash, final OnResponseListener<byte[]> response) {
+        URL url;
+        try {
+            url = new URL(fConfig.GetUrl() + "/objects/" + hash.substring(0, 2) + "/" + hash.substring(2));
+        }
+        catch (MalformedURLException e) {
+            response.onError(e.getClass().getName() +": " +e.getMessage(), e);
+            return;
+        }
+        protoInflateGet(url, new OnResponseListener<byte[]>() {
+            @Override
+            public void onResponse(byte []result) {
+                response.onResponse(result);
+            }
+
+            @Override
+            public void onError(String msg, Throwable e) {
+                response.onError(msg, e);
+            }
+        });
+    }
+}

+ 22 - 0
app/src/main/java/info/knacki/pass/git/GitInterface.java

@@ -0,0 +1,22 @@
+package info.knacki.pass.git;
+
+import info.knacki.pass.git.entities.GitCommit;
+import info.knacki.pass.git.entities.GitObject;
+import info.knacki.pass.git.entities.GitRef;
+
+public interface GitInterface {
+    interface OnResponseListener<T> {
+        void onResponse(T result);
+        void onError(String msg, Throwable e);
+    }
+
+    interface OnStreamResponseListener<T> extends OnResponseListener<T> {
+        void onMsg(String message);
+    }
+
+    void GetRefs(OnResponseListener<GitRef[]> callback);
+    void Pull(OnStreamResponseListener<Void> response);
+    void FetchCommit(GitRef ref, final OnResponseListener<GitCommit> response);
+    void FetchTree(GitCommit ci, OnResponseListener<GitObject.GitTree> response);
+    void PullHash(String ref, OnResponseListener<byte[]> response);
+}

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

@@ -0,0 +1,15 @@
+package info.knacki.pass.git;
+
+import android.content.Context;
+
+import java.nio.channels.UnsupportedAddressTypeException;
+
+import info.knacki.pass.settings.SettingsManager;
+
+public class GitInterfaceFactory {
+    public static GitInterface factory(Context ctx, SettingsManager.Git config) {
+        if (config.GetUrl().startsWith("http://") || config.GetUrl().startsWith("https://"))
+            return new DumbGitInterface(config);
+        return null;
+    }
+}

+ 49 - 0
app/src/main/java/info/knacki/pass/git/entities/GitCommit.java

@@ -0,0 +1,49 @@
+package info.knacki.pass.git.entities;
+
+public class GitCommit {
+    private String fCommit;
+    private String fTree;
+    private String fParent;
+    private String fAuthor;
+    private String fCommitter;
+    private String fMessage;
+
+    public GitCommit(String sha1Content) {
+        int pos = sha1Content.indexOf('\0');
+
+        if (pos >= 0) {
+            fCommit = sha1Content.substring(0, pos);
+            sha1Content = sha1Content.substring(pos +1);
+        } else {
+            fCommit = null;
+        }
+        StringBuilder message = null;
+        for (String line: sha1Content.split("\n")) {
+            if (message != null)
+                message.append(line).append('\n');
+            else if ("".equals(line))
+                message = new StringBuilder();
+            else if (line.startsWith("tree"))
+                fTree = Util.RemoveHead(line);
+            else if (line.startsWith("parent"))
+                fParent = Util.RemoveHead(line);
+            else if (line.startsWith("author"))
+                fAuthor = Util.RemoveHead(line);
+            else if (line.startsWith("committer"))
+                fCommitter = Util.RemoveHead(line);
+        }
+        fMessage = message == null ? "" : message.toString();
+    }
+
+    public String GetMessage() {
+        return fMessage;
+    }
+
+    public String GetTree() {
+        return fTree;
+    }
+
+    public String GetParent() {
+        return fParent;
+    }
+}

+ 54 - 0
app/src/main/java/info/knacki/pass/git/entities/GitObject.java

@@ -0,0 +1,54 @@
+package info.knacki.pass.git.entities;
+
+import java.util.HashMap;
+
+public class GitObject {
+    public final char[] fMode;
+    public final String fName;
+    public final byte[] fSha1;
+
+    public static class GitBlob extends GitObject {
+        private GitBlob(char[] mode, String name, byte []sha1) {
+            super(mode, name, sha1);
+        }
+    }
+
+    public static class GitTree extends GitObject {
+        protected HashMap<String, GitObject> items = null;
+
+        private GitTree(char[] mode, String name, byte []sha1) {
+            super(mode, name, sha1);
+        }
+
+        public GitTree(String sha1) {
+            super(new char[] { '4', '0', '0', '0', '0' }, "", sha1.getBytes());
+        }
+
+        public GitTree Initialize() {
+            items = new HashMap<>();
+            return this;
+        }
+
+        public boolean IsInitialized() {
+            return null != items;
+        }
+
+        public GitTree AddItem(GitObject item) {
+            items.put(item.fName, item);
+            return this;
+        }
+    }
+
+    private GitObject(char[] mode, String name, byte[] sha1) {
+        fMode = mode;
+        fName = name;
+        fSha1 = sha1;
+    }
+
+    public static GitObject factory(char[] mode, String name, byte[]sha1) {
+        if (mode[0] == '4') {
+            return new GitTree(mode, name, sha1);
+        }
+        return new GitBlob(mode, name, sha1);
+    }
+}

+ 31 - 0
app/src/main/java/info/knacki/pass/git/entities/GitRef.java

@@ -0,0 +1,31 @@
+package info.knacki.pass.git.entities;
+
+public class GitRef {
+    protected String fHash;
+    protected String fBranch;
+
+    public GitRef(String hash, String branch) {
+        fHash = hash;
+        fBranch = branch;
+    }
+
+    public String GetBranch() {
+        return fBranch;
+    }
+
+    public String GetHash() {
+        return fHash;
+    }
+
+    public String GetBranchName() {
+        int branchIndex = fBranch.lastIndexOf('/');
+        if (branchIndex == -1)
+            return fBranch;
+        return fBranch.substring(branchIndex + 1);
+    }
+
+    @Override
+    public String toString() {
+        return "ref {" + fHash + "} for branch {" + fBranch + "}";
+    }
+}

+ 10 - 0
app/src/main/java/info/knacki/pass/git/entities/Util.java

@@ -0,0 +1,10 @@
+package info.knacki.pass.git.entities;
+
+public class Util {
+    public static String RemoveHead(String in) {
+        int pos = in.indexOf(' ');
+        if (pos >= 0)
+            return in.substring(pos +1);
+        return in;
+    }
+}

+ 75 - 0
app/src/main/java/info/knacki/pass/git/ui/GitPullActivity.java

@@ -0,0 +1,75 @@
+package info.knacki.pass.git.ui;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import info.knacki.pass.R;
+import info.knacki.pass.git.GitInterface;
+import info.knacki.pass.git.GitInterfaceFactory;
+import info.knacki.pass.settings.SettingsManager;
+
+public class GitPullActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final SettingsManager.VCS versioning = SettingsManager.GetVCS(this);
+
+        setContentView(R.layout.activity_git_pull);
+        setTitle(R.string.pref_vcs_git_pull);
+
+        if (null == versioning || !(versioning instanceof SettingsManager.Git)) {
+            finish();
+            return;
+        }
+        GitInterface gitInterface = GitInterfaceFactory.factory(this, (SettingsManager.Git) versioning);
+        if (gitInterface != null) {
+            gitInterface.Pull(new GitInterface.OnStreamResponseListener<Void>() {
+                @Override
+                public void onMsg(final String msg) {
+                    GitPullActivity.this.runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            TextView logView = findViewById(R.id.logView);
+                            logView.append(msg +"\n");
+                        }
+                    });
+                }
+
+                @Override
+                public void onResponse(Void result) {
+                    GitPullActivity.this.runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            ProgressBar pg = findViewById(R.id.progressBar);
+                            pg.setIndeterminate(false);
+                            pg.setMax(1);
+                            pg.setProgress(1);
+                        }
+                    });
+                }
+
+                @Override
+                public void onError(final String msg, Throwable e) {
+                    GitPullActivity.this.runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            ProgressBar pg = findViewById(R.id.progressBar);
+                            pg.setIndeterminate(false);
+                            pg.setMax(1);
+                            pg.setProgress(1);
+                            TextView logView = findViewById(R.id.logView);
+                            logView.append(msg +"\n");
+                        }
+                    });
+                }
+            });
+        }
+    }
+}

+ 109 - 3
app/src/main/java/info/knacki/pass/settings/SettingsManager.java

@@ -2,11 +2,19 @@ package info.knacki.pass.settings;
 
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.util.JsonReader;
+import android.util.MalformedJsonException;
 
+import org.json.JSONStringer;
+
+import java.io.IOException;
+import java.io.StringReader;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 public class SettingsManager {
+    private static final Logger log = Logger.getLogger(SettingsManager.class.getName());
+
     public static class EncryptionType {
         public static final EncryptionType TYPE_RAW = new EncryptionType(0);
         public static final EncryptionType TYPE_FINGERPRINT = new EncryptionType(1);
@@ -43,7 +51,6 @@ public class SettingsManager {
 
     public static abstract class VCS {
         public static VCS factory(String system, String data) {
-            Logger.getAnonymousLogger().log(Level.WARNING, "System : " +system +", data: " +data);
             if ("git".equals(system))
                 return new Git(data);
             return null;
@@ -54,17 +61,117 @@ public class SettingsManager {
     }
 
     public static class Git extends VCS {
+        protected String fUrl;
+        protected String fUser;
+        protected String fPassword;
+        protected String fBranch;
+
         Git(String data) {
+            if (null == data || data.isEmpty()) {
+                reset();
+                return;
+            }
+
+            try {
+                JsonReader reader = new JsonReader(new StringReader(data));
+                if (!reader.hasNext())
+                    throw new MalformedJsonException(data);
+                reader.beginArray();
+                if (!reader.hasNext())
+                    throw new MalformedJsonException(data);
+                reader.beginObject();
+                while (reader.hasNext()) {
+                    String key = reader.nextName();
+                    if ("url".equals(key))
+                        fUrl = reader.nextString();
+                    else if ("user".equals(key))
+                        fUser = reader.nextString();
+                    else if ("pass".equals(key))
+                        fPassword = reader.nextString();
+                    else if ("branch".equals(key))
+                        fBranch = reader.nextString();
+                }
+                reader.close();
+            }
+            catch (MalformedJsonException e) {
+                log.log(Level.SEVERE, e.getMessage(), e);
+                reset();
+                return;
+            }
+            catch (IOException e) {
+                log.log(Level.SEVERE, e.getMessage(), e);
+                reset();
+                return;
+            }
         }
 
         public Git() {
+            reset();
+        }
+
+        private void reset() {
+            fUrl = "";
+            fBranch = "";
+            fUser = "";
+            fPassword = "";
         }
 
         public String GetName() {
             return "git";
         }
+
+        public String SetUrl(String url) {
+            if (url != null)
+                fUrl = url;
+            return fUrl;
+        }
+        public String GetUrl() {
+            return fUrl;
+        }
+
+        public String SetPassword(String pass) {
+            fPassword = pass;
+            return fPassword;
+        }
+
+        public String GetPassword() {
+            return fPassword;
+        }
+
+        public String SetUser(String user) {
+            return fUser = user;
+        }
+
+        public String GetUser() {
+            return fUser;
+        }
+
+        public String SetBranch(String branch) {
+            return fBranch = branch;
+        }
+
+        public String GetBranch() {
+            return fBranch;
+        }
+
+        public boolean HasAuthentification() {
+            return !"".equals(fUser);
+        }
+
+        private static String jsonEscape(String in) {
+            //FIXME
+            return in;
+        }
+
         public String GetData() {
-            return "";
+            StringBuilder sb = new StringBuilder();
+            sb.append("[{")
+                    .append("\"url\":\"").append(jsonEscape(fUrl)).append("\",")
+                    .append("\"user\":\"").append(jsonEscape(fUser)).append("\",")
+                    .append("\"pass\":\"").append(jsonEscape(fPassword)).append("\",")
+                    .append("\"branch\":\"").append(jsonEscape(fBranch)).append("\"")
+                    .append("}]");
+            return sb.toString();
         }
     }
 
@@ -89,7 +196,6 @@ public class SettingsManager {
 
     public static void SetVCS(Context ctx, VCS vcs) {
         final SharedPreferences.Editor prefs = GetPrefManager(ctx).edit();
-        Logger.getAnonymousLogger().log(Level.WARNING, "save System : " +(vcs == null ? "null" : vcs.GetName()));
         if (vcs != null)
             prefs.putString(VCS.class.getSimpleName(), vcs.GetName()).putString(VCS.class.getSimpleName()+"_data", vcs.GetData()).commit();
         else

+ 181 - 2
app/src/main/java/info/knacki/pass/settings/ui/SettingsActivity.java

@@ -6,6 +6,7 @@ import android.content.Intent;
 import android.content.res.Configuration;
 import android.os.Build;
 import android.os.Bundle;
+import android.preference.EditTextPreference;
 import android.preference.ListPreference;
 import android.preference.Preference;
 import android.preference.PreferenceActivity;
@@ -14,12 +15,17 @@ import android.support.v7.app.ActionBar;
 import android.preference.PreferenceFragment;
 import android.view.MenuItem;
 import android.support.v4.app.NavUtils;
+import android.widget.Toast;
 
 import info.knacki.pass.R;
+import info.knacki.pass.git.GitInterface;
+import info.knacki.pass.git.GitInterfaceFactory;
+import info.knacki.pass.git.entities.GitRef;
+import info.knacki.pass.git.ui.GitPullActivity;
 import info.knacki.pass.settings.SettingsManager;
 
+import java.util.HashMap;
 import java.util.List;
-import java.util.logging.Level;
 import java.util.logging.Logger;
 
 /**
@@ -34,6 +40,7 @@ import java.util.logging.Logger;
  * API Guide</a> for more information on developing a Settings UI.
  */
 public class SettingsActivity extends AppCompatPreferenceActivity {
+    private static final Logger log = Logger.getLogger(SettingsActivity.class.getName());
 
     /**
      * Helper method to determine if the device has an extra-large screen. For
@@ -209,12 +216,54 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
                     }
                 }
             });
+            findPreference(getResources().getString(R.string.id_vcs_git_url)).setOnPreferenceChangeListener(new PrefListener() {
+                @Override
+                void savePref(Preference preference, Object o) {
+                    SettingsManager.Git git = (SettingsManager.Git) SettingsManager.GetVCS(getActivity());
+                    git.SetUrl(((String) o).trim());
+                    SettingsManager.SetVCS(getActivity(), git);
+                }
+            });
 
+            findPreference(getResources().getString(R.string.id_vcs_git_user)).setOnPreferenceChangeListener(new PrefListener() {
+                @Override
+                void savePref(Preference preference, Object o) {
+                    SettingsManager.Git git = (SettingsManager.Git) SettingsManager.GetVCS(getActivity());
+                    git.SetUser(((String) o).trim());
+                    SettingsManager.SetVCS(getActivity(), git);
+                }
+            });
+            findPreference(getResources().getString(R.string.id_vcs_git_pass)).setOnPreferenceChangeListener(new PrefListener() {
+                @Override
+                void savePref(Preference preference, Object o) {
+                    SettingsManager.Git git = (SettingsManager.Git) SettingsManager.GetVCS(getActivity());
+                    git.SetPassword(((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) {
+                    SettingsManager.Git git = (SettingsManager.Git) SettingsManager.GetVCS(getActivity());
+                    git.SetBranch(((String) o).trim());
+                    SettingsManager.SetVCS(getActivity(), git);
+                }
+            });
+
+            findPreference(getResources().getString(R.string.id_vcs_git_pull)).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    startActivity(new Intent(getActivity(), GitPullActivity.class));
+                    return true;
+                }
+            });
             reload();
         }
 
+        HashMap<String, Preference> hiddenPreferences = new HashMap<>();
+
         protected void reload() {
-            SettingsManager.VCS versioning = SettingsManager.GetVCS(getActivity());
+            final SettingsManager.VCS versioning = SettingsManager.GetVCS(getActivity());
 
             if (versioning != null) {
                 ((SwitchPreference) findPreference(getResources().getString(R.string.id_vcs_enable))).setChecked(true);
@@ -223,12 +272,142 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
                 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);
+                    }
+                    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);
+                    }
+                    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);
+                    }
+                    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);
+                    }
+                    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);
+                    }
+                } 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);
+                    }
                 }
             } else {
                 ((SwitchPreference) findPreference(getResources().getString(R.string.id_vcs_enable))).setChecked(false);
                 ListPreference vcsType = (ListPreference) findPreference(getResources().getString(R.string.id_vcs_list));
                 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);
+            }
+        }
+
+        protected void populateBranches(final ListPreference gitBranches, final SettingsManager.Git versioning) {
+            gitBranches.setEnabled(false);
+            GitInterface gitInterface = GitInterfaceFactory.factory(getActivity(), versioning);
+            if (gitInterface != null) {
+                gitInterface.GetRefs(new GitInterface.OnResponseListener<GitRef[]>() {
+                    @Override
+                    public void onResponse(final GitRef[] result) {
+                        getActivity().runOnUiThread(new Runnable() {
+                            @Override
+                            public void run() {
+                                CharSequence[] refNames = new CharSequence[result.length];
+                                CharSequence[] refs = new CharSequence[result.length];
+                                int selected = -1;
+
+                                for (int i =0; i < result.length; ++i) {
+                                    refNames[i] = result[i].GetBranchName   ();
+                                    refs[i] = result[i].GetBranch();
+                                    if (result[i].GetBranch().equals(versioning.GetBranch()))
+                                        selected = i;
+                                }
+                                gitBranches.setEntries(refNames);
+                                gitBranches.setEntryValues(refs);
+                                gitBranches.setEnabled(true);
+                                if (selected >= 0) {
+                                    gitBranches.setSummary(refNames[selected]);
+                                    gitBranches.setValueIndex(selected);
+                                }
+                            }
+                        });
+                    }
+
+                    @Override
+                    public void onError(final String msg, final Throwable e) {
+                        getActivity().runOnUiThread(new Runnable() {
+                            @Override
+                            public void run() {
+                                Toast.makeText(getActivity(), msg, Toast.LENGTH_LONG).show();
+                            }
+                        });
+                    }
+                });
+            } else {
+                gitBranches.setEntryValues(new CharSequence[]{});
+                gitBranches.setEntries(new CharSequence[] {});
+                gitBranches.setEnabled(false);
             }
         }
 

+ 13 - 0
app/src/main/java/info/knacki/pass/ui/MainActivity.java

@@ -1,7 +1,9 @@
 package info.knacki.pass.ui;
 
+import android.Manifest;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.support.v4.app.ActivityCompat;
 import android.support.v7.app.AppCompatActivity;
 import android.os.Bundle;
 import android.view.Menu;
@@ -16,6 +18,7 @@ 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.io.FileInterfaceFactory;
@@ -106,6 +109,14 @@ public class MainActivity extends AppCompatActivity implements PasswordClickList
                 .show();
     }
 
+    static {
+        BridgeUtils.LoadLibraries();
+    }
+
+    void requestPermissions() {
+        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.INTERNET}, 1);
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -114,6 +125,8 @@ public class MainActivity extends AppCompatActivity implements PasswordClickList
         setTitle(R.string.title);
         vPasswordListView = new PasswordListView(this, getApplicationContext().getFilesDir().getAbsolutePath() +"/" +getResources().getString(R.string.data_dir_name));
         ((ScrollView)findViewById(R.id.passwordListContainer)).addView(vPasswordListView);
+
+        requestPermissions();
     }
 
     @Override

+ 19 - 0
app/src/main/res/layout/activity_git_pull.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <ProgressBar
+        android:id="@+id/progressBar"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:indeterminate="true"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+    <android.support.v7.widget.AppCompatTextView
+        android:id="@+id/logView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:inputType="textMultiLine" />
+</LinearLayout>

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

@@ -23,6 +23,11 @@
     <string name="pref_header_Encryption">Chiffrement</string>
     <string name="pref_header_VCS">VCS</string>
     <string name="pref_title_enctype">Méthode de chiffrement</string>
+    <string name="pref_vcs_url">Addresse du dépot git</string>
+    <string name="pref_vcs_git_user_title">Nom d'utilisateur</string>
+    <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>
     <array name="pref_enctype_title">
         <item>Pas de chiffrement</item>
         <item>Empreinte digitale</item>

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

@@ -32,6 +32,11 @@
     <string name="pref_vcs_title">Enable password versioning</string>
     <string name="pref_vcs_enable">pref_vcs_enable</string>
     <string name="pref_vcs_list_title">Versioning tool</string>
+    <string name="pref_vcs_url">Git repo address</string>
+    <string name="pref_vcs_git_user_title">Username</string>
+    <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>
     <array name="pref_vcs_list_keys">
         <item>GIT</item>
     </array>

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

@@ -15,4 +15,9 @@
     <string name="id_enctype">id_enctype</string>
     <string name="id_vcs_enable">id_vcs_enable</string>
     <string name="id_vcs_list">id_vcs_list</string>
+    <string name="id_vcs_git_url">id_vcs_git_url</string>
+    <string name="id_vcs_git_user">id_vcs_git_user</string>
+    <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>
 </resources>

+ 18 - 2
app/src/main/res/xml/pref_vcs.xml

@@ -7,13 +7,29 @@
         android:title="@string/pref_vcs_title" />
 
     <ListPreference
-        android:defaultValue="-1"
         android:entries="@array/pref_vcs_list_keys"
         android:entryValues="@array/pref_vcs_list_values"
         android:key="@string/id_vcs_list"
         android:negativeButtonText="@null"
         android:positiveButtonText="@null"
-        android:enabled="false"
         android:title="@string/pref_vcs_list_title" />
+    <EditTextPreference
+        android:key="@string/id_vcs_git_url"
+        android:title="@string/pref_vcs_url"
+        />
 
+    <EditTextPreference
+        android:key="@string/id_vcs_git_user"
+        android:title="@string/pref_vcs_git_user_title"/>
+    <EditTextPreference
+        android:key="@string/id_vcs_git_pass"
+        android:title="@string/pref_vcs_git_pass_title"
+        android:inputType="textPassword"/>
+
+    <ListPreference
+        android:key="@string/id_vcs_git_branches"
+        android:title="@string/pref_vcs_branch_title"
+        />
+
+    <Preference android:title="@string/pref_vcs_git_pull" android:key="@string/id_vcs_git_pull" />
 </PreferenceScreen>

+ 0 - 2
build.gradle

@@ -8,8 +8,6 @@ buildscript {
     }
     dependencies {
         classpath 'com.android.tools.build:gradle:3.1.4'
-        
-
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
     }