Browse Source

Merge branch 'issue-52' of isundil/pass into master

isundil 7 years ago
parent
commit
3b7bb6b446

+ 30 - 3
app/src/main/java/info/knacki/pass/git/BaseGitProtocol.java

@@ -1,5 +1,7 @@
 package info.knacki.pass.git;
 
+import java.util.logging.Logger;
+
 import info.knacki.pass.git.entities.GitCommit;
 import info.knacki.pass.git.entities.GitRef;
 import info.knacki.pass.io.CharsetHelper;
@@ -9,12 +11,13 @@ import info.knacki.pass.settings.SettingsManager;
 
 public abstract class BaseGitProtocol implements GitInterface {
     protected final SettingsManager.Git fConfig;
+    private final static Logger log = Logger.getLogger(BaseGitProtocol.class.getName());
 
     BaseGitProtocol(SettingsManager.Git config) {
         fConfig = config;
     }
 
-    abstract protected void PushPack(GitCommit commit, Pacman pack, OnStreamResponseListener<Void> resp);
+    abstract protected void PushPack(GitCommit commit, Pacman pack, String branch, OnStreamResponseListener<Void> resp);
     abstract protected void FetchCommit(GitRef ref, OnStreamResponseListener<GitCommit> response);
 
     void GetHeadCommitFromRef(GitRef myRef, final OnStreamResponseListener<GitCommit> response) {
@@ -41,7 +44,7 @@ public abstract class BaseGitProtocol implements GitInterface {
         }
     }
 
-    private GitRef GetRefFromBranchName(GitRef[] refs, String branchName) {
+    protected GitRef GetRefFromBranchName(GitRef[] refs, String branchName) {
         if (refs != null)
             for (GitRef ref: refs)
                 if (ref.GetBranch().equals(branchName))
@@ -80,6 +83,30 @@ public abstract class BaseGitProtocol implements GitInterface {
 
     @Override
     public void PushCommitBuilder(GitCommit.Builder commit, OnStreamResponseListener<Void> resp) {
-        PushPack(commit.Build(), new Pacman(commit.PreparePack()), resp);
+        PushPack(commit.Build(), new Pacman(commit.PreparePack()), fConfig.GetBranch(), resp);
+    }
+
+    protected abstract void OnBranchCreated();
+
+    @Override
+    public void CreateBranch(String branchName, OnStreamResponseListener<GitRef[]> callback) {
+        final GitCommit.Builder commit = new GitCommit.Builder(fConfig.GetUsername(), fConfig.GetUserEmail(), "init");
+        PushPack(commit.Build(), new Pacman(commit.PreparePack()), "refs/heads/" +branchName, new OnStreamResponseListener<Void>() {
+            @Override
+            public void OnMsg(String message) {
+                callback.OnMsg(message);
+            }
+
+            @Override
+            public void OnResponse(Void result) {
+                OnBranchCreated();
+                GetRefs(callback);
+            }
+
+            @Override
+            public void OnError(String msg, Throwable e) {
+                callback.OnError(msg, e);
+            }
+        });
     }
 }

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

@@ -11,4 +11,5 @@ public interface GitInterface {
     void FetchHead(final OnStreamResponseListener<GitCommit> response);
     void FetchBlob(GitObject.GitBlob blob, OnStreamResponseListener<byte[]> response);
     void PushCommitBuilder(GitCommit.Builder commit, OnStreamResponseListener<Void> resp);
+    void CreateBranch(String branchName, OnStreamResponseListener<GitRef[]> callback);
 }

+ 30 - 21
app/src/main/java/info/knacki/pass/git/HttpGitProtocol.java

@@ -38,20 +38,24 @@ class HttpGitProtocol extends BaseGitProtocol {
             protoGet(url, fConfig, 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;
+                    if (result.length() == 0) {
+                        fRefsCache = new GitRef[0];
+                    } else {
+                        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;
+                                }
+                            }
+                            if (fRefsCache[i] == null) {
+                                fRefsCache = null;
+                                callback.OnError("Cannot read references from repository", null);
+                                return;
                             }
-                        }
-                        if (fRefsCache[i] == null) {
-                            fRefsCache = null;
-                            callback.OnError("Cannot read references from repository", null);
-                            return;
                         }
                     }
                     callback.OnResponse(fRefsCache);
@@ -171,16 +175,15 @@ class HttpGitProtocol extends BaseGitProtocol {
     }
 
     @Override
-    public void PushPack(final GitCommit commit, Pacman pack, final OnStreamResponseListener<Void> response) {
+    public void PushPack(final GitCommit commit, Pacman pack, String branch, final OnStreamResponseListener<Void> response) {
         GetRefs(new OnResponseListener<GitRef[]>() {
             @Override
             public void OnResponse(final GitRef[] result) {
-                final GitRef myRef = GetHeadRef(result);
-                if (myRef == null) {
-                    response.OnError("Branch " +fConfig.GetBranch() + " not found on remote for pushing", null);
-                    return;
-                }
-                response.OnMsg("Pushing over " +myRef.GetBranch() +" revision " +myRef.GetHash());
+                final GitRef myRef = GetRefFromBranchName(result, branch);
+                if (myRef == null)
+                    response.OnMsg("Creating branch " +branch);
+                else
+                    response.OnMsg("Pushing over " +myRef.GetBranch() +" revision " +myRef.GetHash());
                 try {
                     HashMap<String, String> headers = new HashMap<>();
                     headers.put("Content-Type", "application/x-git-receive-pack-request");
@@ -199,7 +202,8 @@ class HttpGitProtocol extends BaseGitProtocol {
                         @Override
                         public void OnResponse(OutputStream result) {
                             try {
-                                result.write(GitLine(myRef.GetHash() + " " + GitSha1.BytesToString(GitSha1.getRawSha1OfPackable(commit)) +" " +myRef.GetBranch()));
+                                String prev = myRef == null ? "0000000000000000000000000000000000000000 " : (myRef.GetHash() +" ");
+                                result.write(GitLine(prev +GitSha1.BytesToString(GitSha1.getRawSha1OfPackable(commit)) +" " +branch));
                                 result.write(GitLine(null));
                                 if (!pack.Write(result)) {
                                     OnError("Pack error", null);
@@ -230,4 +234,9 @@ class HttpGitProtocol extends BaseGitProtocol {
             }
         });
     }
+
+    @Override
+    protected void OnBranchCreated() {
+        fRefsCache = null;
+    }
 }

+ 54 - 57
app/src/main/java/info/knacki/pass/git/SSHGitProtocol.java

@@ -27,7 +27,7 @@ import info.knacki.pass.settings.SettingsManager;
 public class SSHGitProtocol extends BaseGitProtocol {
     private final static Logger log = Logger.getLogger(SSHGitProtocol.class.getName());
     private final SSHUrl fRepoUrl;
-
+    private GitRef[] fRefsCache = null;
     private final class ActiveSSHWrapper {
         final SSHConnection fConnection;
         final OutputStream fStdin;
@@ -42,8 +42,6 @@ public class SSHGitProtocol extends BaseGitProtocol {
         }
     }
 
-    private ActiveSSHWrapper fActiveConnection = null;
-
     private final class SSHUrl implements SSHConnection.ConnectionParams {
         private final String repoHost;
         final String repoName;
@@ -148,8 +146,12 @@ public class SSHGitProtocol extends BaseGitProtocol {
         return references.toArray(refs);
     }
 
-    private void ListenForErrors(InputStream stderr, OnErrorListener OnError) {
-        (new Thread() {
+    private void ListenForErrors(InputStream stderr, OnErrorListener onError) {
+        ListenForErrors(stderr, onError, true);
+    }
+
+    private void ListenForErrors(InputStream stderr, OnErrorListener onError, boolean separateThread) {
+        Thread thread = (new Thread() {
             @Override
             public void run() {
                 BufferedReader reader = new BufferedReader(new InputStreamReader(stderr));
@@ -157,36 +159,39 @@ public class SSHGitProtocol extends BaseGitProtocol {
 
                 try {
                     while ((line = reader.readLine()) != null) {
-                        OnError.OnError(line, null);
+                        onError.OnError(line, null);
                     }
                 }
                 catch (IOException e) {
                     log.log(Level.SEVERE, "Cannot read from stderr", e);
                 }
             }
-        }).start();
+        });
+
+        if (separateThread)
+            thread.start();
+        else
+            thread.run();
     }
 
     @Override
     public void GetRefs(OnResponseListener<GitRef[]> callback) {
-        if (fActiveConnection != null) {
-            GitRef[] refs = GetRefs(fActiveConnection, callback);
-            if (refs != null)
-                callback.OnResponse(refs);
-        } else {
-            AppendableInputStream stdin = new AppendableInputStream();
-
-            SSHFactory
-                    .createInstance(fRepoUrl, "git-upload-pack " + fRepoUrl.repoName)
-                    .SetOnConnectionReadyListener((connection, stdout, stderr) -> {
-                        ListenForErrors(stderr, (msg, exc) -> log.severe(msg));
-                        GitRef[] refs = GetRefs(new ActiveSSHWrapper(connection, stdin.GetWriter(), stdout, stderr), callback);
-                        connection.disconnect();
-                        if (refs != null)
-                            callback.OnResponse(refs);
-                    })
-                    .connect();
+        if (fRefsCache != null) {
+            callback.OnResponse(fRefsCache);
+            return;
         }
+        AppendableInputStream stdin = new AppendableInputStream();
+
+        SSHFactory
+                .createInstance(fRepoUrl, "git-upload-pack " + fRepoUrl.repoName)
+                .SetOnConnectionReadyListener((connection, stdout, stderr) -> {
+                    ListenForErrors(stderr, (msg, exc) -> log.severe(msg));
+                    fRefsCache = GetRefs(new ActiveSSHWrapper(connection, stdin.GetWriter(), stdout, stderr), callback);
+                    connection.disconnect();
+                    if (fRefsCache != null)
+                        callback.OnResponse(fRefsCache);
+                })
+                .connect();
     }
 
     private byte[] PullPackData(ActiveSSHWrapper sshWrapper, String hash, OnStreamResponseListener<byte[]> listener) {
@@ -212,22 +217,17 @@ public class SSHGitProtocol extends BaseGitProtocol {
     }
 
     private void PullPackData(String hash, OnStreamResponseListener<byte[]> response) {
-        if (fActiveConnection != null) {
-            byte[] result = PullPackData(fActiveConnection, hash, response);
-            response.OnResponse(result);
-        } else {
-            AppendableInputStream stdin = new AppendableInputStream();
-
-            SSHFactory
-                    .createInstance(fRepoUrl, "git-upload-pack " + fRepoUrl.repoName)
-                    .SetOnConnectionReadyListener((connection, stdout, stderr) -> {
-                        ListenForErrors(stderr, (msg, exc) -> log.severe(msg));
-                        byte[] data = PullPackData(new ActiveSSHWrapper(connection, stdin.GetWriter(), stdout, stderr), hash, response);
-                        connection.disconnect();
-                        response.OnResponse(data);
-                    })
-                    .connect();
-        }
+        AppendableInputStream stdin = new AppendableInputStream();
+
+        SSHFactory
+                .createInstance(fRepoUrl, "git-upload-pack " + fRepoUrl.repoName)
+                .SetOnConnectionReadyListener((connection, stdout, stderr) -> {
+                    ListenForErrors(stderr, (msg, exc) -> log.severe(msg));
+                    byte[] data = PullPackData(new ActiveSSHWrapper(connection, stdin.GetWriter(), stdout, stderr), hash, response);
+                    connection.disconnect();
+                    response.OnResponse(data);
+                })
+                .connect();
     }
 
     @Override
@@ -258,24 +258,19 @@ public class SSHGitProtocol extends BaseGitProtocol {
     }
 
     @Override
-    protected void PushPack(GitCommit commit, Pacman pack, OnStreamResponseListener<Void> resp) {
-        if (fActiveConnection != null)
-            fActiveConnection.fConnection.disconnect();
+    protected void PushPack(GitCommit commit, Pacman pack, String branch, OnStreamResponseListener<Void> resp) {
         AppendableInputStream in = new AppendableInputStream();
         SSHFactory
             .createInstance(fRepoUrl, "git-receive-pack " +fRepoUrl.repoName)
             .SetOnConnectionReadyListener((connection, stdout, stderr) -> {
-                fActiveConnection = new ActiveSSHWrapper(connection, in.GetWriter(), stdout, stderr);
-                GitRef ref = GetHeadRef(GetRefs(fActiveConnection, resp));
-                if (ref == null) {
-                    resp.OnError("Cannot push to " +fRepoUrl.repoName +": branch ref not found", null);
-                    return;
-                }
-                ListenForErrors(stdout, (msg, e) -> resp.OnMsg(msg));
+                ActiveSSHWrapper activeSSHWrapper = new ActiveSSHWrapper(connection, in.GetWriter(), stdout, stderr);
+                fRefsCache = null;
+                GitRef ref = GetRefFromBranchName(GetRefs(activeSSHWrapper, resp), branch);
                 ListenForErrors(stderr, (msg, e) -> resp.OnMsg(msg));
                 try {
                     OutputStream w = in.GetWriter();
-                    w.write(GitLine(ref.GetHash() + " " + GitSha1.BytesToString(GitSha1.getRawSha1OfPackable(commit)) + " " + ref.GetBranch()));
+                    String prev = ref == null ? "0000000000000000000000000000000000000000" : ref.GetHash();
+                    w.write(GitLine(prev + " " + GitSha1.BytesToString(GitSha1.getRawSha1OfPackable(commit)) + " " + branch));
                     w.write(GitLine(null));
                     if (!pack.Write(w)) {
                         resp.OnError("Pack error", null);
@@ -285,6 +280,7 @@ public class SSHGitProtocol extends BaseGitProtocol {
                 catch (IOException e) {
                     resp.OnError("Cannot write pack: " +e.getMessage(), e);
                 }
+                ListenForErrors(stdout, (msg, e) -> resp.OnMsg(msg), false);
                 resp.OnResponse(null);
             })
             .connect(in);
@@ -305,8 +301,8 @@ public class SSHGitProtocol extends BaseGitProtocol {
         SSHFactory
             .createInstance(fRepoUrl, "git-upload-pack " +fRepoUrl.repoName)
             .SetOnConnectionReadyListener((connection, stdout, stderr) -> {
-                fActiveConnection = new ActiveSSHWrapper(connection, stdin.GetWriter(), stdout, stderr);
-                GitRef ref = GetHeadRef(GetRefs(fActiveConnection, response));
+                ActiveSSHWrapper SSHWrapper = new ActiveSSHWrapper(connection, stdin.GetWriter(), stdout, stderr);
+                GitRef ref = GetHeadRef(GetRefs(SSHWrapper, response));
                 if (ref == null)
                     return;
                 GetHeadCommitFromRef(ref, new OnStreamResponseListener<GitCommit>() {
@@ -317,19 +313,20 @@ public class SSHGitProtocol extends BaseGitProtocol {
 
                     @Override
                     public void OnResponse(GitCommit result) {
-                        fActiveConnection.fConnection.disconnect();
-                        fActiveConnection = null;
                         response.OnResponse(result);
                     }
 
                     @Override
                     public void OnError(String msg, Throwable e) {
-                        fActiveConnection.fConnection.disconnect();
-                        fActiveConnection = null;
                         response.OnError(msg, e);
                     }
                 });
             })
             .connect(stdin);
     }
+
+    @Override
+    protected void OnBranchCreated() {
+        fRefsCache = null;
+    }
 }

+ 8 - 2
app/src/main/java/info/knacki/pass/git/entities/GitCommit.java

@@ -50,7 +50,7 @@ public class GitCommit implements GitPackable {
     protected GitCommit(GitCommit parent, String author, String committer, String msg) {
         fAuthor = author;
         fCommitter = committer;
-        fParent = parent.fHash;
+        fParent = parent != null ? parent.fHash : null;
         fMessage = msg;
         fHash = null;
         fTime = new Date();
@@ -97,7 +97,7 @@ public class GitCommit implements GitPackable {
         if (fTime != null)
             dateStr = " " +(fTime.getTime() / 1000) +" " +(new SimpleDateFormat("Z", Locale.US).format(fTime));
         String data = "tree " +(fTree == null ? GitSha1.BytesToString(fTreeHash) : GitSha1.BytesToString(fTree.GetHash())) +"\n" +
-                "parent " +GetParent() +"\n" +
+                (GetParent() != null ? ("parent " +GetParent() +"\n") : "")+
                 "author " +GetAuthor() +dateStr +"\n" +
                 "committer " +GetCommitter() +dateStr +"\n" +
                 "\n" +
@@ -124,6 +124,12 @@ public class GitCommit implements GitPackable {
             fTree = new GitObject.GitTree(null, parent.GetTree());
         }
 
+        public Builder(String author, String authorEmail, String message) {
+            String aut = author +" <" +authorEmail +">";
+            co = new GitCommit(null, aut, aut, message);
+            fTree = GitObject.GitTree.CreateEmpty();
+        }
+
         public GitCommit Build() {
             co.fTree = fTree;
             for (GitPackable go: PreparePack()) {

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

@@ -139,6 +139,11 @@ public abstract class GitObject implements Comparable<GitObject>, GitPackable {
             }
         }
 
+        public static GitTree CreateEmpty() {
+            GitTree tree = new GitTree(null);
+            return tree.Initialize();
+        }
+
         public GitTree Initialize() {
             fItems = new HashMap<>();
             fItemOrder = new ArrayDeque<>();

+ 58 - 26
app/src/main/java/info/knacki/pass/settings/ui/SettingsActivity.java

@@ -52,6 +52,7 @@ import info.knacki.pass.io.pgp.GPGUtil;
 import info.knacki.pass.settings.SettingsManager;
 import info.knacki.pass.ui.GitPullActivity;
 import info.knacki.pass.ui.alertPrompt.AlertPromptGenerator;
+import info.knacki.pass.ui.alertPrompt.views.SimpleTextEdit;
 import info.knacki.pass.ui.alertPrompt.views.TextView;
 import info.knacki.pass.ui.alertPrompt.views.UsernameAndEmail;
 import info.knacki.pass.ui.passwordPicker.PasswordPickerFactory;
@@ -331,19 +332,41 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
             findPreference(getResources().getString(R.string.id_vcs_git_branches)).setOnPreferenceChangeListener(new PrefListener() {
                 @Override
                 void SavePref(Object o) {
-                    SettingsManager.Git git = (SettingsManager.Git) SettingsManager.GetVCS(getActivity());
-                    git.SetBranch(((String) o).trim());
-                    SettingsManager.SetVCS(getActivity(), git);
+                    String branch = (String) o;
+                    if (branch.equals("0000")) {
+                        AlertPromptGenerator.StaticMake(getActivity())
+                                .setView(new SimpleTextEdit(getActivity())
+                                        .setText("master"))
+                                .setTitle(R.string.new_branch)
+                                .setPositiveButton(R.string.ok, (d, v) -> CreateBranch(((SimpleTextEdit) v).getStr().trim()))
+                                .setNegativeButton(R.string.cancel, null)
+                                .show();
+                    } else {
+                        SettingsManager.Git git = (SettingsManager.Git) SettingsManager.GetVCS(getActivity());
+                        git.SetBranch(branch.trim());
+                        SettingsManager.SetVCS(getActivity(), git);
+                    }
                 }
             });
 
             findPreference(getResources().getString(R.string.id_vcs_git_pull)).setOnPreferenceClickListener(preference -> {
-                startActivity(new Intent(getActivity(), GitPullActivity.class));
+                GitPullActivity.StartSyncActivity(getActivity());
                 return true;
             });
+        }
+
+        @Override
+        public void onResume() {
+            super.onResume();
             reload();
         }
 
+        private final void CreateBranch(String branchName) {
+            if (branchName.equals(""))
+                return;
+            GitPullActivity.StartCreateBranchActivity(getActivity(), branchName);
+        }
+
         final SparseArray<Preference> hiddenPreferences = new SparseArray<>();
 
         private void RemovePrefs(int... prefIds) {
@@ -395,7 +418,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
 
                     FindPreference(R.string.id_vcs_git_commit_info_category);
                     ListPreference gitBranches = (ListPreference) FindPreference(R.string.id_vcs_git_branches);
-                    populateBranches(gitBranches, (SettingsManager.Git) versioning);
+                    PopulateBranches(gitBranches, (SettingsManager.Git) versioning);
 
                     FindPreference(R.string.id_vcs_git_pull);
 
@@ -437,13 +460,13 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
             }
         }
 
-        protected void clearBranches(final ListPreference gitBranches) {
+        protected void ClearBranches(final ListPreference gitBranches) {
             gitBranches.setEntryValues(new CharSequence[]{});
             gitBranches.setEntries(new CharSequence[]{});
             gitBranches.setEnabled(false);
         }
 
-        protected void populateBranches(final ListPreference gitBranches, final SettingsManager.Git versioning) {
+        protected void PopulateBranches(final ListPreference gitBranches, final SettingsManager.Git versioning) {
             gitBranches.setEnabled(false);
             if (versioning != null) {
                 GitInterface gitInterface;
@@ -452,30 +475,17 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
                 }
                 catch (GitInterfaceFactory.GitInterfaceException e) {
                     Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show();
-                    clearBranches(gitBranches);
+                    ClearBranches(gitBranches);
                     return;
                 }
                 gitInterface.GetRefs(new OnResponseListener<GitRef[]>() {
                     @Override
                     public void OnResponse(final GitRef[] result) {
                         getActivity().runOnUiThread(() -> {
-                            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);
-                            }
+                            if (result == null)
+                                ClearBranches(gitBranches);
+                            else
+                                PopulateBranches(gitBranches, versioning, result);
                         });
                     }
 
@@ -485,7 +495,29 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
                     }
                 });
             } else {
-                clearBranches(gitBranches);
+                ClearBranches(gitBranches);
+            }
+        }
+
+        private void PopulateBranches(ListPreference gitBranches, SettingsManager.Git versioning, GitRef[] result) {
+            CharSequence[] refNames = new CharSequence[result.length +1];
+            CharSequence[] refs = new CharSequence[result.length +1];
+            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;
+            }
+            refNames[result.length] = getResources().getString(R.string.new_branch);
+            refs[result.length] = "0000";
+            gitBranches.setEntries(refNames);
+            gitBranches.setEntryValues(refs);
+            gitBranches.setEnabled(true);
+            if (selected >= 0) {
+                gitBranches.setSummary(refNames[selected]);
+                gitBranches.setValueIndex(selected);
             }
         }
 

+ 148 - 70
app/src/main/java/info/knacki/pass/ui/GitPullActivity.java

@@ -1,5 +1,7 @@
 package info.knacki.pass.ui;
 
+import android.content.Context;
+import android.content.Intent;
 import android.os.Bundle;
 import android.support.v7.app.AppCompatActivity;
 import android.widget.ProgressBar;
@@ -24,6 +26,7 @@ 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.FileUtils;
 import info.knacki.pass.io.OnResponseListener;
 import info.knacki.pass.io.OnStreamResponseListener;
@@ -33,13 +36,23 @@ import info.knacki.pass.ui.alertPrompt.AlertPrompt;
 import info.knacki.pass.ui.alertPrompt.AlertPromptGenerator;
 import info.knacki.pass.ui.alertPrompt.views.ConflictView;
 
+import static info.knacki.pass.settings.SettingsManager.GetVCS;
+
 public class GitPullActivity extends AppCompatActivity {
     private final static Logger log = Logger.getLogger(GitPullActivity.class.getName());
     public final static String COMMIT_MSG = "Android pass sync";
     public static String LOCAL_GIT_HASH_VERSION_FILE;
 
-    private GitInterface fGitInterface;
-    private GitCommit fHeadCommit;
+    public final static String GIT_COMMAND = GitPullActivity.class.getName() +"_GIT_COMMAND";
+    public final static String BRANCH_NAME = GitPullActivity.class.getName() +"_BRANCH_NAME";
+    public final static char COMMAND_SYNC = 1;
+    public final static char COMMAND_CREATE_BRANCH = 2;
+
+    private class Status {
+        HashSet<String> filesToPush = new HashSet<>();
+        HashMap<String, GitObject.GitBlob> filesToPull = new HashMap<>();
+        HashMap<String, GitObject.GitBlob> conflictingFiles = new HashMap<>();
+    }
 
     private void OnMsg(final String msg) {
         log.info(msg);
@@ -49,59 +62,124 @@ public class GitPullActivity extends AppCompatActivity {
         });
     }
 
+    public static void StartSyncActivity(Context ctx) {
+        Intent i = new Intent(ctx, GitPullActivity.class);
+        i.putExtra(GIT_COMMAND, COMMAND_SYNC);
+        ctx.startActivity(i);
+    }
+
+    public static void StartCreateBranchActivity(Context ctx, String branchName) {
+        Intent i = new Intent(ctx, GitPullActivity.class);
+        i.putExtra(GIT_COMMAND, COMMAND_CREATE_BRANCH);
+        i.putExtra(BRANCH_NAME, branchName);
+        ctx.startActivity(i);
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        final SettingsManager.VCS versioning = SettingsManager.GetVCS(this);
+        final SettingsManager.VCS versioning = GetVCS(this);
 
         setContentView(R.layout.activity_git_pull);
         setTitle(R.string.pref_vcs_git_pull);
+        findViewById(R.id.close_bt).setOnClickListener(v -> GitPullActivity.this.finish());
+
+        LOCAL_GIT_HASH_VERSION_FILE = PathUtils.GetGitFile(this);
 
         if (!(versioning instanceof SettingsManager.Git)) {
             finish();
             return;
         }
-        LOCAL_GIT_HASH_VERSION_FILE = PathUtils.GetGitFile(this);
+        final GitInterface gitInterface;
         try {
-            fGitInterface = GitInterfaceFactory.factory((SettingsManager.Git) versioning);
-            fGitInterface.FetchHead(new OnStreamResponseListener<GitCommit>() {
-                @Override
-                public void OnMsg(final String msg) {
-                    GitPullActivity.this.OnMsg(msg);
-                }
-
-                @Override
-                public void OnResponse(GitCommit result) {
-                    fHeadCommit = result;
-                    GitPullActivity.this.runOnUiThread(GitPullActivity.this::OnTreeStructureFetched);
-                }
-
-                @Override
-                public void OnError(final String msg, Throwable e) {
-                    GitPullActivity.this.runOnUiThread(() -> {
-                        OnMsg(msg);
-                        FinishLoading();
-                    });
-                }
-            });
+            gitInterface = GitInterfaceFactory.factory((SettingsManager.Git) versioning);
         } catch (GitInterfaceFactory.GitInterfaceException e) {
             GitPullActivity.this.runOnUiThread(() -> {
                 OnMsg(e.getMessage());
                 FinishLoading();
             });
+            return;
         }
-        findViewById(R.id.close_bt).setOnClickListener(v -> GitPullActivity.this.finish());
+
+        final char command = getIntent().getCharExtra(GIT_COMMAND, (char) 0);
+        if (COMMAND_SYNC == command)
+            FetchHead(gitInterface);
+        else if (COMMAND_CREATE_BRANCH == command)
+            CreateBranch(gitInterface, getIntent().getStringExtra(BRANCH_NAME));
+        else
+            finish();
+    }
+
+    private void CreateBranch(GitInterface gitInterface, String branchName) {
+        if (branchName == null)
+            finish();
+        OnMsg("Creating branch " +branchName);
+        final String trimmedBranchName = branchName.trim();
+        if ("".equals(trimmedBranchName)) {
+            OnMsg("Invalid branch name");
+            finish();
+        }
+
+        OnMsg("Pushing to origin");
+        gitInterface.CreateBranch(trimmedBranchName, new OnStreamResponseListener<GitRef[]>() {
+            @Override
+            public void OnResponse(GitRef[] result) {
+                runOnUiThread(GitPullActivity.this::FinishLoading);
+                for (GitRef i: result)
+                    if (i.GetBranchName().equals(trimmedBranchName)) {
+                        SettingsManager.VCS git = SettingsManager.GetVCS(GitPullActivity.this);
+                        if (git instanceof SettingsManager.Git) {
+                            ((SettingsManager.Git) git).SetBranch(i.GetBranch());
+                            SettingsManager.SetVCS(GitPullActivity.this, git);
+                            OnMsg("Branch set up to track " +i.GetBranch());
+                        }
+                    }
+            }
+
+            @Override
+            public void OnError(String msg, Throwable e) {
+                runOnUiThread(() -> {
+                    GitPullActivity.this.OnMsg(msg);
+                    FinishLoading();
+                });
+            }
+
+            @Override
+            public void OnMsg(String msg) {
+                runOnUiThread(() -> GitPullActivity.this.OnMsg(msg));
+            }
+        });
     }
 
-    protected void OnTreeStructureFetched() {
+    private void FetchHead(GitInterface gitInterface) {
+        gitInterface.FetchHead(new OnStreamResponseListener<GitCommit>() {
+            @Override
+            public void OnMsg(final String msg) {
+                GitPullActivity.this.OnMsg(msg);
+            }
+
+            @Override
+            public void OnResponse(GitCommit result) {
+                GitPullActivity.this.runOnUiThread(() -> GitPullActivity.this.OnTreeStructureFetched(gitInterface, result));
+            }
+
+            @Override
+            public void OnError(final String msg, Throwable e) {
+                GitPullActivity.this.runOnUiThread(() -> {
+                    OnMsg(msg);
+                    FinishLoading();
+                });
+            }
+        });
+    }
+
+    private void OnTreeStructureFetched(GitInterface gitInterface, GitCommit headCommit) {
         final GitLocal localVersion = new GitLocal(new File(LOCAL_GIT_HASH_VERSION_FILE));
-        HashMap<String, GitObject.GitBlob> filesToPull = new HashMap<>();
-        Set<String> filesToPush = new HashSet<>();
-        HashMap<String, GitObject.GitBlob> conflictingFiles = new HashMap<>();
+        Status status = new Status();
 
         OnMsg("Done reading remote tree");
         OnMsg("Building change list");
-        for (Map.Entry<String, GitObject.GitBlob>i: fHeadCommit.GetTree().FindAllBlobs().entrySet()) {
+        for (Map.Entry<String, GitObject.GitBlob>i: headCommit.GetTree().FindAllBlobs().entrySet()) {
             final String remoteKnownHash = localVersion.GetHash(i.getKey(), "");
             final String remoteHash = GitSha1.BytesToString(i.getValue().GetHash());
             final String currentHash = GitSha1.BytesToString(GitSha1.getRawSha1OfFile(new File(PathUtils.GetPassDir(this) +i.getKey())));
@@ -112,47 +190,47 @@ public class GitPullActivity extends AppCompatActivity {
             if (remoteChanged && localChanged) {
                 // Conflict (but file can still has the same content)
                 if (currentHash.equals(remoteHash))
-                    filesToPull.put(i.getKey(), i.getValue());
+                    status.filesToPull.put(i.getKey(), i.getValue());
                 else
-                    conflictingFiles.put(i.getKey(), i.getValue());
+                    status.conflictingFiles.put(i.getKey(), i.getValue());
             } else if (remoteChanged) {
                 // remote changed
-                filesToPull.put(i.getKey(), i.getValue());
+                status.filesToPull.put(i.getKey(), i.getValue());
             } else if (localChanged) {
                 // local changed
-                filesToPush.add(i.getKey());
+                status.filesToPush.add(i.getKey());
             }
         }
         for (String i: localVersion.FileNames()) {
-            if (fHeadCommit.GetTree().GetObjectFullPath(i) == null) {
+            if (headCommit.GetTree().GetObjectFullPath(i) == null) {
                 log.finer("removed from remote " +i);
                 final String currentHash = GitSha1.BytesToString(GitSha1.getRawSha1OfFile(new File(PathUtils.GetPassDir(this) +i)));
                 final boolean localChanged = !currentHash.equals(localVersion.GetHash(i, ""));
 
                 if (!localChanged || "".equals(currentHash))
-                    filesToPull.put(i, null);
+                    status.filesToPull.put(i, null);
                 else
-                    conflictingFiles.put(i, null);
+                    status.conflictingFiles.put(i, null);
             }
         }
-        CheckNewFiles(localVersion, new File(PathUtils.GetPassDir(this)), "", filesToPull.keySet(), filesToPush, conflictingFiles.keySet());
-        if (conflictingFiles.isEmpty())
-            SyncFiles(localVersion, filesToPull, filesToPush);
+        CheckNewFiles(localVersion, new File(PathUtils.GetPassDir(this)), "", status);
+        if (status.conflictingFiles.isEmpty())
+            SyncFiles(localVersion, gitInterface, headCommit, status);
         else
-            AskForConflicts(localVersion, conflictingFiles, filesToPull, filesToPush);
+            AskForConflicts(localVersion, gitInterface, headCommit, status);
     }
 
-    int CheckNewFiles(GitLocal localVersion, File root, String rootPath, Set<String> filesToPull, Set<String> filesToPush, Set<String> conflicts) {
+    private int CheckNewFiles(GitLocal localVersion, File root, String rootPath, Status status) {
         int newFiles = 0;
         for (final File i: root.listFiles()) {
             if (!PathUtils.IsHidden(i.getAbsolutePath())) {
                 if (i.isDirectory()) {
-                    newFiles += CheckNewFiles(localVersion, i, rootPath + "/" + i.getName(), filesToPull, filesToPush, conflicts);
+                    newFiles += CheckNewFiles(localVersion, i, rootPath + "/" + i.getName(), status);
                 } else if (i.isFile()) {
                     String path = rootPath + "/" + i.getName();
-                    if (!localVersion.HasHash(path) && !filesToPull.contains(path) && !conflicts.contains(path)) {
+                    if (!localVersion.HasHash(path) && !status.filesToPull.containsKey(path) && !status.conflictingFiles.containsKey(path)) {
                         // New file
-                        filesToPush.add(path);
+                        status.filesToPush.add(path);
                         newFiles++;
                     }
                 }
@@ -161,7 +239,7 @@ public class GitPullActivity extends AppCompatActivity {
         return newFiles;
     }
 
-    void AskForConflicts(final GitLocal localVersion, final HashMap<String, GitObject.GitBlob> conflicts, final HashMap<String, GitObject.GitBlob> filesToPull, final Set<String> filesToPush) {
+    private void AskForConflicts(final GitLocal localVersion, final GitInterface gitInterface, final GitCommit headCommit, final Status status) {
         runOnUiThread(() -> {
             AlertPrompt pt = AlertPromptGenerator.StaticMake(GitPullActivity.this)
                     .setCancelable(true)
@@ -169,19 +247,19 @@ public class GitPullActivity extends AppCompatActivity {
                     .setPositiveButton(R.string.ok, (dialogInterface, v) -> {
                         ConflictView.ConflictViewResult viewResult = ((ConflictView) v).GetResult();
                         for (String s: viewResult.fUseTheir) {
-                            filesToPull.put(s, conflicts.get(s));
+                            status.filesToPull.put(s, status.conflictingFiles.get(s));
                         }
-                        filesToPush.addAll(viewResult.fUseMine);
-                        SyncFiles(localVersion, filesToPull, filesToPush);
+                        status.filesToPush.addAll(viewResult.fUseMine);
+                        SyncFiles(localVersion, gitInterface, headCommit, status);
                     })
                     .setTitle(R.string.conflictingFiles);
-            ConflictView view = new ConflictView(GitPullActivity.this, pt, conflicts.keySet());
+            ConflictView view = new ConflictView(GitPullActivity.this, pt, status.conflictingFiles.keySet());
             pt.setView(view).show();
             view.UpdateButtonState();
         });
     }
 
-    void RmEmptyDirs(File dir, boolean isRoot) {
+    private void RmEmptyDirs(File dir, boolean isRoot) {
         File[] content = dir.listFiles();
 
         if (null == content || content.length == 0) {
@@ -194,7 +272,7 @@ public class GitPullActivity extends AppCompatActivity {
                 RmEmptyDirs(i, false);
     }
 
-    void FinishLoading() {
+    private void FinishLoading() {
         ProgressBar pg = findViewById(R.id.progressBar);
         pg.setIndeterminate(false);
         pg.setMax(1);
@@ -203,7 +281,7 @@ public class GitPullActivity extends AppCompatActivity {
         findViewById(R.id.close_bt).setEnabled(true);
     }
 
-    void SyncFiles(final GitLocal localVersion, final HashMap<String, GitObject.GitBlob> filesToPull, final Set<String> filesToPush) {
+    private void SyncFiles(final GitLocal localVersion, final GitInterface gitInterface, final GitCommit headCommit, final Status status) {
         final OnStreamResponseListener<Void> allDone = new OnStreamResponseListener<Void>() {
             @Override
             public void OnResponse(Void result) {
@@ -225,21 +303,21 @@ public class GitPullActivity extends AppCompatActivity {
         };
 
         final Runnable afterFetching = () -> {
-            if (filesToPush.size() > 0) {
+            if (status.filesToPush.size() > 0) {
                 GitPullActivity.this.OnMsg("Updating remote repository");
-                PushBlobs(filesToPush, localVersion, allDone);
+                PushBlobs(gitInterface, headCommit, status.filesToPush, localVersion, allDone);
             } else {
                 GitPullActivity.this.OnMsg("Nothing to push");
                 allDone.OnResponse(null);
             }
         };
 
-        if (filesToPull.isEmpty()) {
+        if (status.filesToPull.isEmpty()) {
             OnMsg("Nothing to pull");
             afterFetching.run();
         } else {
             OnMsg("Updating local repository");
-            DownloadBlobs(filesToPull, localVersion, new OnStreamResponseListener<Void>() {
+            DownloadBlobs(gitInterface, status.filesToPull, localVersion, new OnStreamResponseListener<Void>() {
                 @Override
                 public void OnResponse(Void result) {
                     afterFetching.run();
@@ -258,7 +336,7 @@ public class GitPullActivity extends AppCompatActivity {
         }
     }
 
-    void DownloadBlobs(Map<String, GitObject.GitBlob> blobs, final GitLocal localVersion, final OnStreamResponseListener<Void> resp) {
+    private void DownloadBlobs(GitInterface gitInterface, Map<String, GitObject.GitBlob> blobs, final GitLocal localVersion, final OnStreamResponseListener<Void> resp) {
         if (blobs.size() == 0) {
             resp.OnResponse(null);
             return;
@@ -280,7 +358,7 @@ public class GitPullActivity extends AppCompatActivity {
                     WriteFile(filename, localVersion, blob, result);
                     logView.append("Done fetching " +files.peek().getValue().GetFilename() +"\n");
                     files.pop();
-                    DownloadNext(files, localVersion, _this, resp);
+                    DownloadNext(gitInterface, files, localVersion, _this, resp);
                 });
             }
 
@@ -292,7 +370,7 @@ public class GitPullActivity extends AppCompatActivity {
 
                 files.pop();
                 if (!files.empty()) {
-                    fGitInterface.FetchBlob(files.peek().getValue(), this);
+                    gitInterface.FetchBlob(files.peek().getValue(), this);
                 } else {
                     resp.OnResponse(null);
                 }
@@ -304,27 +382,27 @@ public class GitPullActivity extends AppCompatActivity {
             }
         };
 
-        DownloadNext(files, localVersion, downloader, resp);
+        DownloadNext(gitInterface, files, localVersion, downloader, resp);
     }
 
-    void DownloadNext(Stack<Map.Entry<String, GitObject.GitBlob>> files, GitLocal localCache, OnStreamResponseListener<byte[]> downloader, OnResponseListener<Void> resp) {
+    private void DownloadNext(GitInterface gitInterface, Stack<Map.Entry<String, GitObject.GitBlob>> files, GitLocal localCache, OnStreamResponseListener<byte[]> downloader, OnResponseListener<Void> resp) {
         if (!files.empty()) {
             if (files.peek().getValue() == null) {
                 // remove file
                 String filename = files.pop().getKey();
                 FileUtils.TrashFile(new File(PathUtils.GetPassDir(this) +filename));
                 localCache.remove(filename);
-                DownloadNext(files, localCache, downloader, resp);
+                DownloadNext(gitInterface, files, localCache, downloader, resp);
                 log.info("Removed file " +filename);
             } else {
-                fGitInterface.FetchBlob(files.peek().getValue(), downloader);
+                gitInterface.FetchBlob(files.peek().getValue(), downloader);
             }
         } else {
             resp.OnResponse(null);
         }
     }
 
-    void WriteFile(String filename, final GitLocal localVersion, GitObject.GitBlob blob, byte[] result) {
+    private void WriteFile(String filename, final GitLocal localVersion, GitObject.GitBlob blob, byte[] result) {
         File f = new File(PathUtils.GetPassDir(this) +filename);
         FileUtils.MkDir(f.getParentFile(), true);
         final int chunkSize = 1024;
@@ -343,16 +421,16 @@ public class GitPullActivity extends AppCompatActivity {
         localVersion.SetHash(filename, GitSha1.BytesToString(blob.GetHash()));
     }
 
-    void PushBlobs(final Set<String> files, final GitLocal localVersion, final OnStreamResponseListener<Void> resp) {
+    private void PushBlobs(final GitInterface gitInterface, final GitCommit headCommit, final Set<String> files, final GitLocal localVersion, final OnStreamResponseListener<Void> resp) {
         if (files.isEmpty()) {
             resp.OnMsg("Nothing to commit");
             resp.OnResponse(null);
         } else {
-            SettingsManager.Git config = (SettingsManager.Git) SettingsManager.GetVCS(this);
-            final GitCommit.Builder commit = new GitCommit.Builder(fHeadCommit, config.GetUsername(), config.GetUserEmail(), COMMIT_MSG);
+            SettingsManager.Git config = (SettingsManager.Git) GetVCS(this);
+            final GitCommit.Builder commit = new GitCommit.Builder(headCommit, config.GetUsername(), config.GetUserEmail(), COMMIT_MSG);
             for (String i : files)
                 commit.AddFile(i, new File(PathUtils.GetPassDir(this) + i));
-            fGitInterface.PushCommitBuilder(commit, new OnStreamResponseListener<Void>() {
+            gitInterface.PushCommitBuilder(commit, new OnStreamResponseListener<Void>() {
                 @Override
                 public void OnMsg(String message) {
                     resp.OnMsg(message);

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

@@ -132,7 +132,7 @@ public class MainActivity extends AppCompatActivity implements PasswordEditListe
             case 0:
                 break;
             case R.id.id_sync:
-                startActivity(new Intent(this, GitPullActivity.class));
+                GitPullActivity.StartSyncActivity(this);
                 break;
             case R.id.new_folder:
                 CreateNewFolder();

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

@@ -55,6 +55,7 @@
         <item>Fort</item>
         <item>Personalisé</item>
     </array>
+    <string name="new_branch">Nouvelle branche</string>
     <string name="pref_summary_keyboard_enabled"> </string>
     <string name="pref_summary_enable_keyboard">Autoriser le clavier pour la saisie des mots de passes</string>
     <string name="pref_gpg_title_username_mail">Username</string>

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

@@ -55,6 +55,7 @@
         <item>Strong</item>
         <item>Custom</item>
     </array>
+    <string name="new_branch">New branch</string>
     <string name="pref_summary_keyboard_enabled">Keyboard is enabled</string>
     <string name="pref_summary_enable_keyboard">Allow pass keyboard</string>
     <string name="pref_gpg_title_username_mail">Username</string>