aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/com/zeapo/pwdstore/git
diff options
context:
space:
mode:
authorHarsh Shandilya <msfjarvis@gmail.com>2019-10-01 22:14:28 +0530
committerGitHub <noreply@github.com>2019-10-01 22:14:28 +0530
commit073346c157ec34b750d175c8543f346e6eae94de (patch)
treea55d24ef43563f3a2d03d7938a1e96c1f5cd1caf /app/src/main/java/com/zeapo/pwdstore/git
parentc9dc4034f12b62f4470f92fb13ecd72871f4a5b7 (diff)
downloadHardenedPass-073346c157ec34b750d175c8543f346e6eae94de.tar.gz
HardenedPass-073346c157ec34b750d175c8543f346e6eae94de.zip
Redesign UI and introduce dark theme (#519)
Caveats: - The openpgp preference had to be removed because the open-intents developers are too lazy to update their libraries. Over the coming weeks I will be reimplementing a local solution for this instead. - The autofill dialog is broken but I since it is being worked on in #410 already I'm not going to bother fixing it.
Diffstat (limited to 'app/src/main/java/com/zeapo/pwdstore/git')
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/BreakOutOfDetached.kt8
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/CloneOperation.kt14
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/GitActivity.java726
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/GitActivity.kt660
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt22
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/PullOperation.kt9
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/PushOperation.kt9
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/ResetToRemoteOperation.kt52
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/SyncOperation.kt10
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java14
10 files changed, 752 insertions, 772 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/BreakOutOfDetached.kt b/app/src/main/java/com/zeapo/pwdstore/git/BreakOutOfDetached.kt
index 31c65704..8b23c38e 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/BreakOutOfDetached.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/BreakOutOfDetached.kt
@@ -1,7 +1,7 @@
package com.zeapo.pwdstore.git
import android.app.Activity
-import android.app.AlertDialog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.R
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.GitCommand
@@ -37,7 +37,7 @@ class BreakOutOfDetached(fileDir: File, callingActivity: Activity) : GitOperatio
override fun execute() {
val git = Git(repository)
if (!git.repository.repositoryState.isRebasing) {
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.git_abort_and_push_title))
.setMessage("The repository is not rebasing, no need to push to another branch")
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
@@ -59,7 +59,7 @@ class BreakOutOfDetached(fileDir: File, callingActivity: Activity) : GitOperatio
}
override fun onError(errorMessage: String) {
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage("Error occurred when checking out another branch operation $errorMessage")
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
@@ -68,7 +68,7 @@ class BreakOutOfDetached(fileDir: File, callingActivity: Activity) : GitOperatio
}
override fun onSuccess() {
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.git_abort_and_push_title))
.setMessage("There was a conflict when trying to rebase. " +
"Your local master branch was pushed to another branch named conflicting-master-....\n" +
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/CloneOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/CloneOperation.kt
index bf407838..8489eb1d 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/CloneOperation.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/CloneOperation.kt
@@ -1,11 +1,10 @@
package com.zeapo.pwdstore.git
import android.app.Activity
-import android.app.AlertDialog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.R
import org.eclipse.jgit.api.CloneCommand
import org.eclipse.jgit.api.Git
-
import java.io.File
/**
@@ -23,7 +22,10 @@ class CloneOperation(fileDir: File, callingActivity: Activity) : GitOperation(fi
* @return the current object
*/
fun setCommand(uri: String): CloneOperation {
- this.command = Git.cloneRepository().setCloneAllBranches(true).setDirectory(repository.workTree).setURI(uri)
+ this.command = Git.cloneRepository()
+ .setCloneAllBranches(true)
+ .setDirectory(repository?.workTree)
+ .setURI(uri)
return this
}
@@ -53,14 +55,12 @@ class CloneOperation(fileDir: File, callingActivity: Activity) : GitOperation(fi
}
override fun execute() {
- if (this.provider != null) {
- (this.command as CloneCommand).setCredentialsProvider(this.provider)
- }
+ (this.command as? CloneCommand)?.setCredentialsProvider(this.provider)
GitAsyncTask(callingActivity, true, false, this).execute(this.command)
}
override fun onError(errorMessage: String) {
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage("Error occured during the clone operation, "
+ callingActivity.resources.getString(R.string.jgit_error_dialog_text)
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitActivity.java b/app/src/main/java/com/zeapo/pwdstore/git/GitActivity.java
deleted file mode 100644
index 9ef05f60..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/git/GitActivity.java
+++ /dev/null
@@ -1,726 +0,0 @@
-package com.zeapo.pwdstore.git;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.Spinner;
-import android.widget.TextView;
-
-import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.app.AppCompatActivity;
-
-import com.zeapo.pwdstore.R;
-import com.zeapo.pwdstore.UserPreference;
-import com.zeapo.pwdstore.git.config.SshApiSessionFactory;
-import com.zeapo.pwdstore.utils.PasswordRepository;
-
-import org.apache.commons.io.FileUtils;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class GitActivity extends AppCompatActivity {
- public static final int REQUEST_PULL = 101;
- public static final int REQUEST_PUSH = 102;
- public static final int REQUEST_CLONE = 103;
- public static final int REQUEST_INIT = 104;
- public static final int EDIT_SERVER = 105;
- public static final int REQUEST_SYNC = 106;
- public static final int REQUEST_CREATE = 107;
- public static final int EDIT_GIT_CONFIG = 108;
- public static final int BREAK_OUT_OF_DETACHED = 109;
- private static final String TAG = "GitAct";
- private static final String emailPattern = "^[^@]+@[^@]+$";
- private Activity activity;
- private Context context;
- private String protocol;
- private String connectionMode;
- private String hostname;
- private SharedPreferences settings;
- private SshApiSessionFactory.IdentityBuilder identityBuilder;
- private SshApiSessionFactory.ApiIdentity identity;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- context = getApplicationContext();
- activity = this;
-
- settings = PreferenceManager.getDefaultSharedPreferences(this.context);
-
- protocol = settings.getString("git_remote_protocol", "ssh://");
- connectionMode = settings.getString("git_remote_auth", "ssh-key");
- int operationCode = getIntent().getExtras().getInt("Operation");
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
-
- switch (operationCode) {
- case REQUEST_CLONE:
- case EDIT_SERVER:
- setContentView(R.layout.activity_git_clone);
- setTitle(R.string.title_activity_git_clone);
-
- final Spinner protcol_spinner = findViewById(R.id.clone_protocol);
- final Spinner connection_mode_spinner = findViewById(R.id.connection_mode);
-
- // init the spinner for connection modes
- final ArrayAdapter<CharSequence> connection_mode_adapter = ArrayAdapter.createFromResource(this,
- R.array.connection_modes, android.R.layout.simple_spinner_item);
- connection_mode_adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- connection_mode_spinner.setAdapter(connection_mode_adapter);
- connection_mode_spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
- String selection = ((Spinner) findViewById(R.id.connection_mode)).getSelectedItem().toString();
- connectionMode = selection;
- settings.edit().putString("git_remote_auth", selection).apply();
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> adapterView) {
-
- }
- });
-
- // init the spinner for protocols
- ArrayAdapter<CharSequence> protocol_adapter = ArrayAdapter.createFromResource(this,
- R.array.clone_protocols, android.R.layout.simple_spinner_item);
- protocol_adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- protcol_spinner.setAdapter(protocol_adapter);
- protcol_spinner.setOnItemSelectedListener(
- new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
- protocol = ((Spinner) findViewById(R.id.clone_protocol)).getSelectedItem().toString();
- if (protocol.equals("ssh://")) {
- ((EditText) findViewById(R.id.clone_uri)).setHint("user@hostname:path");
-
- ((EditText) findViewById(R.id.server_port)).setHint(R.string.default_ssh_port);
-
- // select ssh-key auth mode as default and enable the spinner in case it was disabled
- connection_mode_spinner.setSelection(0);
- connection_mode_spinner.setEnabled(true);
-
- // however, if we have some saved that, that's more important!
- if (connectionMode.equalsIgnoreCase("ssh-key")) {
- connection_mode_spinner.setSelection(0);
- } else if (connectionMode.equalsIgnoreCase("OpenKeychain")) {
- connection_mode_spinner.setSelection(2);
- } else {
- connection_mode_spinner.setSelection(1);
- }
- } else {
- ((EditText) findViewById(R.id.clone_uri)).setHint("hostname/path");
-
- ((EditText) findViewById(R.id.server_port)).setHint(R.string.default_https_port);
-
- // select user/pwd auth-mode and disable the spinner
- connection_mode_spinner.setSelection(1);
- connection_mode_spinner.setEnabled(false);
- }
-
- updateURI();
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> adapterView) {
-
- }
- }
- );
-
- if (protocol.equals("ssh://")) {
- protcol_spinner.setSelection(0);
- } else {
- protcol_spinner.setSelection(1);
- }
-
- // init the server information
- final EditText server_url = findViewById(R.id.server_url);
- final EditText server_port = findViewById(R.id.server_port);
- final EditText server_path = findViewById(R.id.server_path);
- final EditText server_user = findViewById(R.id.server_user);
- final EditText server_uri = findViewById(R.id.clone_uri);
-
- server_url.setText(settings.getString("git_remote_server", ""));
- server_port.setText(settings.getString("git_remote_port", ""));
- server_user.setText(settings.getString("git_remote_username", ""));
- server_path.setText(settings.getString("git_remote_location", ""));
-
- server_url.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- if (server_url.isFocused())
- updateURI();
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- }
- });
- server_port.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- if (server_port.isFocused())
- updateURI();
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- }
- });
- server_user.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- if (server_user.isFocused())
- updateURI();
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- }
- });
- server_path.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- if (server_path.isFocused())
- updateURI();
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- }
- });
-
- server_uri.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- if (server_uri.isFocused())
- splitURI();
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- }
- });
-
- if (operationCode == EDIT_SERVER) {
- findViewById(R.id.clone_button).setVisibility(View.INVISIBLE);
- findViewById(R.id.save_button).setVisibility(View.VISIBLE);
- } else {
- findViewById(R.id.clone_button).setVisibility(View.VISIBLE);
- findViewById(R.id.save_button).setVisibility(View.INVISIBLE);
- }
-
- updateURI();
-
- break;
- case EDIT_GIT_CONFIG:
- setContentView(R.layout.activity_git_config);
- setTitle(R.string.title_activity_git_config);
-
- showGitConfig();
- break;
- case REQUEST_PULL:
- syncRepository(REQUEST_PULL);
- break;
-
- case REQUEST_PUSH:
- syncRepository(REQUEST_PUSH);
- break;
-
- case REQUEST_SYNC:
- syncRepository(REQUEST_SYNC);
- break;
- }
-
-
- }
-
- /**
- * Fills in the server_uri field with the information coming from other fields
- */
- private void updateURI() {
- EditText uri = findViewById(R.id.clone_uri);
- EditText server_url = findViewById(R.id.server_url);
- EditText server_port = findViewById(R.id.server_port);
- EditText server_path = findViewById(R.id.server_path);
- EditText server_user = findViewById(R.id.server_user);
-
- if (uri != null) {
- switch (protocol) {
- case "ssh://": {
- String hostname =
- server_user.getText()
- + "@" +
- server_url.getText().toString().trim()
- + ":";
- if (server_port.getText().toString().equals("22")) {
- hostname += server_path.getText().toString();
-
- findViewById(R.id.warn_url).setVisibility(View.GONE);
- } else {
- TextView warn_url = findViewById(R.id.warn_url);
- if (!server_path.getText().toString().matches("/.*") && !server_port.getText().toString().isEmpty()) {
- warn_url.setText(R.string.warn_malformed_url_port);
- warn_url.setVisibility(View.VISIBLE);
- } else {
- warn_url.setVisibility(View.GONE);
- }
- hostname += server_port.getText().toString() + server_path.getText().toString();
- }
-
- if (!hostname.equals("@:")) uri.setText(hostname);
- }
- break;
- case "https://": {
- StringBuilder hostname = new StringBuilder();
- hostname.append(server_url.getText().toString().trim());
-
- if (server_port.getText().toString().equals("443")) {
- hostname.append(server_path.getText().toString());
-
- findViewById(R.id.warn_url).setVisibility(View.GONE);
- } else {
- hostname.append("/");
- hostname.append(server_port.getText().toString())
- .append(server_path.getText().toString());
- }
-
- if (!hostname.toString().equals("@/")) uri.setText(hostname);
- }
- break;
- default:
- break;
- }
-
- }
- }
-
- /**
- * Splits the information in server_uri into the other fields
- */
- private void splitURI() {
- EditText server_uri = findViewById(R.id.clone_uri);
- EditText server_url = findViewById(R.id.server_url);
- EditText server_port = findViewById(R.id.server_port);
- EditText server_path = findViewById(R.id.server_path);
- EditText server_user = findViewById(R.id.server_user);
-
- String uri = server_uri.getText().toString();
- Pattern pattern = Pattern.compile("(.+)@([\\w\\d.]+):([\\d]+)*(.*)");
- Matcher matcher = pattern.matcher(uri);
- if (matcher.find()) {
- int count = matcher.groupCount();
- if (count > 1) {
- server_user.setText(matcher.group(1));
- server_url.setText(matcher.group(2));
- }
- if (count == 4) {
- server_port.setText(matcher.group(3));
- server_path.setText(matcher.group(4));
-
- TextView warn_url = findViewById(R.id.warn_url);
- if (!server_path.getText().toString().matches("/.*") && !server_port.getText().toString().isEmpty()) {
- warn_url.setText(R.string.warn_malformed_url_port);
- warn_url.setVisibility(View.VISIBLE);
- } else {
- warn_url.setVisibility(View.GONE);
- }
- }
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
- updateURI();
- }
-
- @Override
- protected void onDestroy() {
- // Do not leak the service connection
- if (identityBuilder != null) {
- identityBuilder.close();
- identityBuilder = null;
- }
- super.onDestroy();
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.git_clone, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // Handle action bar item clicks here. The action bar will
- // automatically handle clicks on the Home/Up button, so long
- // as you specify a parent activity in AndroidManifest.xml.
- int id = item.getItemId();
- if (id == R.id.user_pref) {
- try {
- Intent intent = new Intent(this, UserPreference.class);
- startActivity(intent);
- } catch (Exception e) {
- System.out.println("Exception caught :(");
- e.printStackTrace();
- }
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- /**
- * Saves the configuration found in the form
- */
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- private boolean saveConfiguration() {
- // remember the settings
- SharedPreferences.Editor editor = settings.edit();
-
- editor.putString("git_remote_server", ((EditText) findViewById(R.id.server_url)).getText().toString());
- editor.putString("git_remote_location", ((EditText) findViewById(R.id.server_path)).getText().toString());
- editor.putString("git_remote_username", ((EditText) findViewById(R.id.server_user)).getText().toString());
- editor.putString("git_remote_protocol", protocol);
- editor.putString("git_remote_auth", connectionMode);
- editor.putString("git_remote_port", ((EditText) findViewById(R.id.server_port)).getText().toString());
- editor.putString("git_remote_uri", ((EditText) findViewById(R.id.clone_uri)).getText().toString());
-
- // 'save' hostname variable for use by addRemote() either here or later
- // in syncRepository()
- hostname = ((EditText) findViewById(R.id.clone_uri)).getText().toString();
- String port = ((EditText) findViewById(R.id.server_port)).getText().toString();
- // don't ask the user, take off the protocol that he puts in
- hostname = hostname.replaceFirst("^.+://", "");
- ((TextView) findViewById(R.id.clone_uri)).setText(hostname);
-
- if (!protocol.equals("ssh://")) {
- hostname = protocol + hostname;
- } else {
- // if the port is explicitly given, jgit requires the ssh://
- if (!port.isEmpty() && !port.equals("22"))
- hostname = protocol + hostname;
-
- // did he forget the username?
- if (!hostname.matches("^.+@.+")) {
- new AlertDialog.Builder(this).
- setMessage(activity.getResources().getString(R.string.forget_username_dialog_text)).
- setPositiveButton(activity.getResources().getString(R.string.dialog_oops), null).
- show();
- return false;
- }
- }
- if (PasswordRepository.isInitialized() && settings.getBoolean("repository_initialized", false)) {
- // don't just use the clone_uri text, need to use hostname which has
- // had the proper protocol prepended
- PasswordRepository.addRemote("origin", hostname, true);
- }
-
- editor.apply();
- return true;
- }
-
- /**
- * Save the repository information to the shared preferences settings
- */
- public void saveConfiguration(View view) {
- if (!saveConfiguration())
- return;
- finish();
- }
-
- private void showGitConfig() {
- // init the server information
- final EditText git_user_name = findViewById(R.id.git_user_name);
- final EditText git_user_email = findViewById(R.id.git_user_email);
- final Button abort = findViewById(R.id.git_abort_rebase);
-
- git_user_name.setText(settings.getString("git_config_user_name", ""));
- git_user_email.setText(settings.getString("git_config_user_email", ""));
-
- // git status
- Repository repo = PasswordRepository.getRepository(PasswordRepository.getRepositoryDirectory(activity.getApplicationContext()));
- if (repo != null) {
- final TextView git_commit_hash = findViewById(R.id.git_commit_hash);
- try {
- ObjectId objectId = repo.resolve(Constants.HEAD);
- Ref ref = repo.getRef("refs/heads/master");
- String head = ref.getObjectId().equals(objectId) ? ref.getName() : "DETACHED";
- git_commit_hash.setText(String.format("%s (%s)", objectId.abbreviate(8).name(), head));
-
- // enable the abort button only if we're rebasing
- abort.setEnabled(repo.getRepositoryState().isRebasing());
- } catch (Exception e) {
- // ignore
- }
- }
- }
-
- private boolean saveGitConfigs() {
- // remember the settings
- SharedPreferences.Editor editor = settings.edit();
-
- String email = ((EditText) findViewById(R.id.git_user_email)).getText().toString();
- editor.putString("git_config_user_email", email);
- editor.putString("git_config_user_name", ((EditText) findViewById(R.id.git_user_name)).getText().toString());
-
- if (!email.matches(emailPattern)) {
- new AlertDialog.Builder(this).
- setMessage(activity.getResources().getString(R.string.invalid_email_dialog_text)).
- setPositiveButton(activity.getResources().getString(R.string.dialog_oops), null).
- show();
- return false;
- }
-
- editor.apply();
- return true;
- }
-
- public void applyGitConfigs(View view) {
- if (!saveGitConfigs())
- return;
-
- String git_user_name = settings.getString("git_config_user_name", "");
- String git_user_email = settings.getString("git_config_user_email", "");
-
- PasswordRepository.setUserName(git_user_name);
- PasswordRepository.setUserEmail(git_user_email);
-
- finish();
- }
-
- public void abortRebase(View view) {
- launchGitOperation(BREAK_OUT_OF_DETACHED);
- }
-
- /**
- * Clones the repository, the directory exists, deletes it
- */
- public void cloneRepository(View view) {
- if (PasswordRepository.getRepository(null) == null) {
- PasswordRepository.initialize(this);
- }
- File localDir = PasswordRepository.getRepositoryDirectory(context);
-
- if (!saveConfiguration())
- return;
-
- // Warn if non-empty folder unless it's a just-initialized store that has just a .git folder
- if (localDir.exists() && localDir.listFiles().length != 0
- && !(localDir.listFiles().length == 1 && localDir.listFiles()[0].getName().equals(".git"))) {
- new AlertDialog.Builder(this).
- setTitle(R.string.dialog_delete_title).
- setMessage(getResources().getString(R.string.dialog_delete_msg) + " " + localDir.toString()).
- setCancelable(false).
- setPositiveButton(R.string.dialog_delete,
- (dialog, id) -> {
- try {
- FileUtils.deleteDirectory(localDir);
- launchGitOperation(REQUEST_CLONE);
- } catch (IOException e) {
- //TODO Handle the exception correctly if we are unable to delete the directory...
- e.printStackTrace();
- new AlertDialog.Builder(GitActivity.this).setMessage(e.getMessage()).show();
- }
-
- dialog.cancel();
- }
- ).
- setNegativeButton(R.string.dialog_do_not_delete,
- (dialog, id) -> dialog.cancel()
- ).
- show();
- } else {
- try {
- // Silently delete & replace the lone .git folder if it exists
- if (localDir.exists() && localDir.listFiles().length == 1 && localDir.listFiles()[0].getName().equals(".git")) {
- try {
- FileUtils.deleteDirectory(localDir);
- } catch (IOException e) {
- e.printStackTrace();
- new AlertDialog.Builder(GitActivity.this).setMessage(e.getMessage()).show();
- }
- }
- } catch (Exception e) {
- //This is what happens when jgit fails :(
- //TODO Handle the diffent cases of exceptions
- e.printStackTrace();
- new AlertDialog.Builder(this).setMessage(e.getMessage()).show();
- }
- launchGitOperation(REQUEST_CLONE);
- }
- }
-
- /**
- * Syncs the local repository with the remote one (either pull or push)
- *
- * @param operation the operation to execute can be REQUEST_PULL or REQUEST_PUSH
- */
- private void syncRepository(int operation) {
- if (settings.getString("git_remote_username", "").isEmpty() ||
- settings.getString("git_remote_server", "").isEmpty() ||
- settings.getString("git_remote_location", "").isEmpty())
- new AlertDialog.Builder(this)
- .setMessage(activity.getResources().getString(R.string.set_information_dialog_text))
- .setPositiveButton(activity.getResources().getString(R.string.dialog_positive), (dialogInterface, i) -> {
- Intent intent = new Intent(activity, UserPreference.class);
- startActivityForResult(intent, REQUEST_PULL);
- })
- .setNegativeButton(activity.getResources().getString(R.string.dialog_negative), (dialogInterface, i) -> {
- // do nothing :(
- setResult(RESULT_OK);
- finish();
- })
- .show();
-
- else {
- // check that the remote origin is here, else add it
- PasswordRepository.addRemote("origin", hostname, false);
- launchGitOperation(operation);
- }
- }
-
- /**
- * Attempt to launch the requested GIT operation. Depending on the configured auth, it may not
- * be possible to launch the operation immediately. In that case, this function may launch an
- * intermediate activity instead, which will gather necessary information and post it back via
- * onActivityResult, which will then re-call this function. This may happen multiple times,
- * until either an error is encountered or the operation is successfully launched.
- *
- * @param operation The type of GIT operation to launch
- */
- protected void launchGitOperation(int operation) {
- GitOperation op;
- File localDir = PasswordRepository.getRepositoryDirectory(context);
-
- try {
-
- // Before launching the operation with OpenKeychain auth, we need to issue several requests
- // to the OpenKeychain API. IdentityBuild will take care of launching the relevant intents,
- // we just need to keep calling it until it returns a completed ApiIdentity.
- if (connectionMode.equalsIgnoreCase("OpenKeychain") && identity == null) {
- // Lazy initialization of the IdentityBuilder
- if (identityBuilder == null) {
- identityBuilder = new SshApiSessionFactory.IdentityBuilder(this);
- }
-
- // Try to get an ApiIdentity and bail if one is not ready yet. The builder will ensure
- // that onActivityResult is called with operation again, which will re-invoke us here
- identity = identityBuilder.tryBuild(operation);
- if (identity == null)
- return;
- }
-
- switch (operation) {
- case REQUEST_CLONE:
- case GitOperation.GET_SSH_KEY_FROM_CLONE:
- op = new CloneOperation(localDir, activity).setCommand(hostname);
- break;
-
- case REQUEST_PULL:
- op = new PullOperation(localDir, activity).setCommand();
- break;
-
- case REQUEST_PUSH:
- op = new PushOperation(localDir, activity).setCommand();
- break;
-
- case REQUEST_SYNC:
- op = new SyncOperation(localDir, activity).setCommands();
- break;
-
- case BREAK_OUT_OF_DETACHED:
- op = new BreakOutOfDetached(localDir, activity).setCommands();
- break;
-
- case SshApiSessionFactory.POST_SIGNATURE:
- return;
-
- default:
- Log.e(TAG, "Operation not recognized : " + operation);
- setResult(RESULT_CANCELED);
- finish();
- return;
- }
-
- op.executeAfterAuthentication(connectionMode,
- settings.getString("git_remote_username", "git"),
- new File(getFilesDir() + "/.ssh_key"),
- identity);
- } catch (Exception e) {
- e.printStackTrace();
- new AlertDialog.Builder(this).setMessage(e.getMessage()).show();
- }
- }
-
- protected void onActivityResult(int requestCode, int resultCode,
- Intent data) {
-
- // In addition to the pre-operation-launch series of intents for OpenKeychain auth
- // that will pass through here and back to launchGitOperation, there is one
- // synchronous operation that happens /after/ the operation has been launched in the
- // background thread - the actual signing of the SSH challenge. We pass through the
- // completed signature to the ApiIdentity, which will be blocked in the other thread
- // waiting for it.
- if (requestCode == SshApiSessionFactory.POST_SIGNATURE && identity != null)
- identity.postSignature(data);
-
- if (resultCode == RESULT_CANCELED) {
- setResult(RESULT_CANCELED);
- finish();
- } else if (resultCode == RESULT_OK) {
- // If an operation has been re-queued via this mechanism, let the
- // IdentityBuilder attempt to extract some updated state from the intent before
- // trying to re-launch the operation.
- if (identityBuilder != null) {
- identityBuilder.consume(data);
- }
- launchGitOperation(requestCode);
- }
- super.onActivityResult(requestCode, resultCode, data);
- }
-
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitActivity.kt b/app/src/main/java/com/zeapo/pwdstore/git/GitActivity.kt
new file mode 100644
index 00000000..11cc4c1b
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/git/GitActivity.kt
@@ -0,0 +1,660 @@
+package com.zeapo.pwdstore.git
+
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
+import android.util.Log
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Spinner
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.AppCompatTextView
+import androidx.preference.PreferenceManager
+import com.google.android.material.button.MaterialButton
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.textfield.TextInputEditText
+import com.zeapo.pwdstore.R
+import com.zeapo.pwdstore.UserPreference
+import com.zeapo.pwdstore.git.config.SshApiSessionFactory
+import com.zeapo.pwdstore.utils.PasswordRepository
+import org.apache.commons.io.FileUtils
+import org.eclipse.jgit.lib.Constants
+import java.io.File
+import java.io.IOException
+import java.util.regex.Pattern
+
+open class GitActivity : AppCompatActivity() {
+ private lateinit var context: Context
+ private lateinit var settings: SharedPreferences
+ private lateinit var protocol: String
+ private lateinit var connectionMode: String
+ private lateinit var hostname: String
+ private var identityBuilder: SshApiSessionFactory.IdentityBuilder? = null
+ private var identity: SshApiSessionFactory.ApiIdentity? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ context = requireNotNull(this)
+
+ settings = PreferenceManager.getDefaultSharedPreferences(this)
+
+ protocol = settings.getString("git_remote_protocol", null) ?: "ssh://"
+ connectionMode = settings.getString("git_remote_auth", null) ?: "ssh-key"
+ hostname = settings.getString("git_remote_location", null) ?: ""
+ val operationCode = intent.extras!!.getInt("Operation")
+
+ supportActionBar!!.setDisplayHomeAsUpEnabled(true)
+
+ when (operationCode) {
+ REQUEST_CLONE, EDIT_SERVER -> {
+ setContentView(R.layout.activity_git_clone)
+ setTitle(R.string.title_activity_git_clone)
+
+ val protcolSpinner = findViewById<Spinner>(R.id.clone_protocol)
+ val connectionModeSpinner = findViewById<Spinner>(R.id.connection_mode)
+
+ // init the spinner for connection modes
+ val connectionModeAdapter = ArrayAdapter.createFromResource(this,
+ R.array.connection_modes, android.R.layout.simple_spinner_item)
+ connectionModeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ connectionModeSpinner.adapter = connectionModeAdapter
+ connectionModeSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(adapterView: AdapterView<*>, view: View, i: Int, l: Long) {
+ val selection = (findViewById<View>(R.id.connection_mode) as Spinner).selectedItem.toString()
+ connectionMode = selection
+ settings.edit().putString("git_remote_auth", selection).apply()
+ }
+
+ override fun onNothingSelected(adapterView: AdapterView<*>) {
+
+ }
+ }
+
+ // init the spinner for protocols
+ val protocolAdapter = ArrayAdapter.createFromResource(this,
+ R.array.clone_protocols, android.R.layout.simple_spinner_item)
+ protocolAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ protcolSpinner.adapter = protocolAdapter
+ protcolSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(adapterView: AdapterView<*>, view: View, i: Int, l: Long) {
+ protocol = (findViewById<View>(R.id.clone_protocol) as Spinner).selectedItem.toString()
+ if (protocol == "ssh://") {
+
+ // select ssh-key auth mode as default and enable the spinner in case it was disabled
+ connectionModeSpinner.setSelection(0)
+ connectionModeSpinner.isEnabled = true
+
+ // however, if we have some saved that, that's more important!
+ when {
+ connectionMode.equals("ssh-key", ignoreCase = true) -> connectionModeSpinner.setSelection(0)
+ connectionMode.equals("OpenKeychain", ignoreCase = true) -> connectionModeSpinner.setSelection(2)
+ else -> connectionModeSpinner.setSelection(1)
+ }
+ } else {
+ // select user/pwd auth-mode and disable the spinner
+ connectionModeSpinner.setSelection(1)
+ connectionModeSpinner.isEnabled = false
+ }
+
+ updateURI()
+ }
+
+ override fun onNothingSelected(adapterView: AdapterView<*>) {
+
+ }
+ }
+
+ if (protocol == "ssh://") {
+ protcolSpinner.setSelection(0)
+ } else {
+ protcolSpinner.setSelection(1)
+ }
+
+ // init the server information
+ val serverUrl = findViewById<TextInputEditText>(R.id.server_url)
+ val serverPort = findViewById<TextInputEditText>(R.id.server_port)
+ val serverPath = findViewById<TextInputEditText>(R.id.server_path)
+ val serverUser = findViewById<TextInputEditText>(R.id.server_user)
+ val serverUri = findViewById<TextInputEditText>(R.id.clone_uri)
+
+ serverUrl.setText(settings.getString("git_remote_server", ""))
+ serverPort.setText(settings.getString("git_remote_port", ""))
+ serverUser.setText(settings.getString("git_remote_username", ""))
+ serverPath.setText(settings.getString("git_remote_location", ""))
+
+ serverUrl.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {}
+
+ override fun onTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {
+ if (serverUrl.isFocused)
+ updateURI()
+ }
+
+ override fun afterTextChanged(editable: Editable) {}
+ })
+ serverPort.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {}
+
+ override fun onTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {
+ if (serverPort.isFocused)
+ updateURI()
+ }
+
+ override fun afterTextChanged(editable: Editable) {}
+ })
+ serverUser.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {}
+
+ override fun onTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {
+ if (serverUser.isFocused)
+ updateURI()
+ }
+
+ override fun afterTextChanged(editable: Editable) {}
+ })
+ serverPath.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {}
+
+ override fun onTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {
+ if (serverPath.isFocused)
+ updateURI()
+ }
+
+ override fun afterTextChanged(editable: Editable) {}
+ })
+
+ serverUri.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {}
+
+ override fun onTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {
+ if (serverUri.isFocused)
+ splitURI()
+ }
+
+ override fun afterTextChanged(editable: Editable) {}
+ })
+
+ if (operationCode == EDIT_SERVER) {
+ findViewById<View>(R.id.clone_button).visibility = View.INVISIBLE
+ findViewById<View>(R.id.save_button).visibility = View.VISIBLE
+ } else {
+ findViewById<View>(R.id.clone_button).visibility = View.VISIBLE
+ findViewById<View>(R.id.save_button).visibility = View.INVISIBLE
+ }
+
+ updateURI()
+ }
+ EDIT_GIT_CONFIG -> {
+ setContentView(R.layout.activity_git_config)
+ setTitle(R.string.title_activity_git_config)
+
+ showGitConfig()
+ }
+ REQUEST_PULL -> syncRepository(REQUEST_PULL)
+
+ REQUEST_PUSH -> syncRepository(REQUEST_PUSH)
+
+ REQUEST_SYNC -> syncRepository(REQUEST_SYNC)
+ }
+ }
+
+ /**
+ * Fills in the server_uri field with the information coming from other fields
+ */
+ private fun updateURI() {
+ val uri = findViewById<TextInputEditText>(R.id.clone_uri)
+ val serverUrl = findViewById<TextInputEditText>(R.id.server_url)
+ val serverPort = findViewById<TextInputEditText>(R.id.server_port)
+ val serverPath = findViewById<TextInputEditText>(R.id.server_path)
+ val serverUser = findViewById<TextInputEditText>(R.id.server_user)
+
+ if (uri != null) {
+ when (protocol) {
+ "ssh://" -> {
+ var hostname = (serverUser.text.toString()
+ + "@" +
+ serverUrl.text.toString().trim { it <= ' ' }
+ + ":")
+ if (serverPort.text.toString() == "22") {
+ hostname += serverPath.text.toString()
+
+ findViewById<View>(R.id.warn_url).visibility = View.GONE
+ } else {
+ val warnUrl = findViewById<AppCompatTextView>(R.id.warn_url)
+ if (!serverPath.text.toString().matches("/.*".toRegex()) && serverPort.text.toString().isNotEmpty()) {
+ warnUrl.setText(R.string.warn_malformed_url_port)
+ warnUrl.visibility = View.VISIBLE
+ } else {
+ warnUrl.visibility = View.GONE
+ }
+ hostname += serverPort.text.toString() + serverPath.text.toString()
+ }
+
+ if (hostname != "@:") uri.setText(hostname)
+ }
+ "https://" -> {
+ val hostname = StringBuilder()
+ hostname.append(serverUrl.text.toString().trim { it <= ' ' })
+
+ if (serverPort.text.toString() == "443") {
+ hostname.append(serverPath.text.toString())
+
+ findViewById<View>(R.id.warn_url).visibility = View.GONE
+ } else {
+ hostname.append("/")
+ hostname.append(serverPort.text.toString())
+ .append(serverPath.text.toString())
+ }
+
+ if (hostname.toString() != "@/") uri.setText(hostname)
+ }
+ else -> {
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Splits the information in server_uri into the other fields
+ */
+ private fun splitURI() {
+ val serverUri = findViewById<TextInputEditText>(R.id.clone_uri)
+ val serverUrl = findViewById<TextInputEditText>(R.id.server_url)
+ val serverPort = findViewById<TextInputEditText>(R.id.server_port)
+ val serverPath = findViewById<TextInputEditText>(R.id.server_path)
+ val serverUser = findViewById<TextInputEditText>(R.id.server_user)
+
+ val uri = serverUri.text.toString()
+ val pattern = Pattern.compile("(.+)@([\\w\\d.]+):([\\d]+)*(.*)")
+ val matcher = pattern.matcher(uri)
+ if (matcher.find()) {
+ val count = matcher.groupCount()
+ if (count > 1) {
+ serverUser.setText(matcher.group(1))
+ serverUrl.setText(matcher.group(2))
+ }
+ if (count == 4) {
+ serverPort.setText(matcher.group(3))
+ serverPath.setText(matcher.group(4))
+
+ val warnUrl = findViewById<AppCompatTextView>(R.id.warn_url)
+ if (!serverPath.text.toString().matches("/.*".toRegex()) && serverPort.text.toString().isNotEmpty()) {
+ warnUrl.setText(R.string.warn_malformed_url_port)
+ warnUrl.visibility = View.VISIBLE
+ } else {
+ warnUrl.visibility = View.GONE
+ }
+ }
+ }
+ }
+
+ public override fun onResume() {
+ super.onResume()
+ updateURI()
+ }
+
+ override fun onDestroy() {
+ // Do not leak the service connection
+ if (identityBuilder != null) {
+ identityBuilder!!.close()
+ identityBuilder = null
+ }
+ super.onDestroy()
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ menuInflater.inflate(R.menu.git_clone, menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.user_pref -> try {
+ val intent = Intent(this, UserPreference::class.java)
+ startActivity(intent)
+ return true
+ } catch (e: Exception) {
+ println("Exception caught :(")
+ e.printStackTrace()
+ }
+
+ android.R.id.home -> {
+ finish()
+ return true
+ }
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ /**
+ * Saves the configuration found in the form
+ */
+ private fun saveConfiguration(): Boolean {
+ // remember the settings
+ val editor = settings.edit()
+
+ editor.putString("git_remote_server", (findViewById<View>(R.id.server_url) as TextInputEditText).text.toString())
+ editor.putString("git_remote_location", (findViewById<View>(R.id.server_path) as TextInputEditText).text.toString())
+ editor.putString("git_remote_username", (findViewById<View>(R.id.server_user) as TextInputEditText).text.toString())
+ editor.putString("git_remote_protocol", protocol)
+ editor.putString("git_remote_auth", connectionMode)
+ editor.putString("git_remote_port", (findViewById<View>(R.id.server_port) as TextInputEditText).text.toString())
+ editor.putString("git_remote_uri", (findViewById<View>(R.id.clone_uri) as TextInputEditText).text.toString())
+
+ // 'save' hostname variable for use by addRemote() either here or later
+ // in syncRepository()
+ hostname = (findViewById<View>(R.id.clone_uri) as TextInputEditText).text.toString()
+ val port = (findViewById<View>(R.id.server_port) as TextInputEditText).text.toString()
+ // don't ask the user, take off the protocol that he puts in
+ hostname = hostname.replaceFirst("^.+://".toRegex(), "")
+ (findViewById<View>(R.id.clone_uri) as TextInputEditText).setText(hostname)
+
+ if (protocol != "ssh://") {
+ hostname = protocol + hostname
+ } else {
+ // if the port is explicitly given, jgit requires the ssh://
+ if (port.isNotEmpty() && port != "22")
+ hostname = protocol + hostname
+
+ // did he forget the username?
+ if (!hostname.matches("^.+@.+".toRegex())) {
+ MaterialAlertDialogBuilder(this)
+ .setMessage(context.getString(R.string.forget_username_dialog_text))
+ .setPositiveButton(context.getString(R.string.dialog_oops), null)
+ .show()
+ return false
+ }
+ }
+ if (PasswordRepository.isInitialized && settings.getBoolean("repository_initialized", false)) {
+ // don't just use the clone_uri text, need to use hostname which has
+ // had the proper protocol prepended
+ PasswordRepository.addRemote("origin", hostname, true)
+ }
+
+ editor.apply()
+ return true
+ }
+
+ /**
+ * Save the repository information to the shared preferences settings
+ */
+ @Suppress("UNUSED_PARAMETER")
+ fun saveConfiguration(view: View) {
+ if (!saveConfiguration())
+ return
+ finish()
+ }
+
+ private fun showGitConfig() {
+ // init the server information
+ val username = findViewById<TextInputEditText>(R.id.git_user_name)
+ val email = findViewById<TextInputEditText>(R.id.git_user_email)
+ val abort = findViewById<MaterialButton>(R.id.git_abort_rebase)
+
+ username.setText(settings.getString("git_config_user_name", ""))
+ email.setText(settings.getString("git_config_user_email", ""))
+
+ // git status
+ val repo = PasswordRepository.getRepository(PasswordRepository.getRepositoryDirectory(context))
+ if (repo != null) {
+ val commitHash = findViewById<AppCompatTextView>(R.id.git_commit_hash)
+ try {
+ val objectId = repo.resolve(Constants.HEAD)
+ val ref = repo.getRef("refs/heads/master")
+ val head = if (ref.objectId.equals(objectId)) ref.name else "DETACHED"
+ commitHash.text = String.format("%s (%s)", objectId.abbreviate(8).name(), head)
+
+ // enable the abort button only if we're rebasing
+ val isRebasing = repo.repositoryState.isRebasing
+ abort.isEnabled = isRebasing
+ abort.alpha = if (isRebasing) 1.0f else 0.5f
+ } catch (e: Exception) {
+ // ignore
+ }
+
+ }
+ }
+
+ private fun saveGitConfigs(): Boolean {
+ // remember the settings
+ val editor = settings.edit()
+
+ val email = (findViewById<View>(R.id.git_user_email) as TextInputEditText).text!!.toString()
+ editor.putString("git_config_user_email", email)
+ editor.putString("git_config_user_name", (findViewById<View>(R.id.git_user_name) as TextInputEditText).text.toString())
+
+ if (!email.matches(emailPattern.toRegex())) {
+ MaterialAlertDialogBuilder(this)
+ .setMessage(context.getString(R.string.invalid_email_dialog_text))
+ .setPositiveButton(context.getString(R.string.dialog_oops), null)
+ .show()
+ return false
+ }
+
+ editor.apply()
+ return true
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun applyGitConfigs(view: View) {
+ if (!saveGitConfigs())
+ return
+ PasswordRepository.setUserName(settings.getString("git_config_user_name", null) ?: "")
+ PasswordRepository.setUserEmail(settings.getString("git_config_user_email", null) ?: "")
+ finish()
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun abortRebase(view: View) {
+ launchGitOperation(BREAK_OUT_OF_DETACHED)
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun resetToRemote(view: View) {
+ launchGitOperation(REQUEST_RESET)
+ }
+
+ /**
+ * Clones the repository, the directory exists, deletes it
+ */
+ @Suppress("UNUSED_PARAMETER")
+ fun cloneRepository(view: View) {
+ if (PasswordRepository.getRepository(null) == null) {
+ PasswordRepository.initialize(this)
+ }
+ val localDir = requireNotNull(PasswordRepository.getRepositoryDirectory(context))
+
+ if (!saveConfiguration())
+ return
+
+ // Warn if non-empty folder unless it's a just-initialized store that has just a .git folder
+ if (localDir.exists() && localDir.listFiles()!!.isNotEmpty()
+ && !(localDir.listFiles()!!.size == 1 && localDir.listFiles()!![0].name == ".git")) {
+ MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.dialog_delete_title)
+ .setMessage(resources.getString(R.string.dialog_delete_msg) + " " + localDir.toString())
+ .setCancelable(false)
+ .setPositiveButton(R.string.dialog_delete
+ ) { dialog, _ ->
+ try {
+ FileUtils.deleteDirectory(localDir)
+ launchGitOperation(REQUEST_CLONE)
+ } catch (e: IOException) {
+ //TODO Handle the exception correctly if we are unable to delete the directory...
+ e.printStackTrace()
+ MaterialAlertDialogBuilder(this).setMessage(e.message).show()
+ }
+
+ dialog.cancel()
+ }
+ .setNegativeButton(R.string.dialog_do_not_delete
+ ) { dialog, _ -> dialog.cancel() }
+ .show()
+ } else {
+ try {
+ // Silently delete & replace the lone .git folder if it exists
+ if (localDir.exists() && localDir.listFiles()!!.size == 1 && localDir.listFiles()!![0].name == ".git") {
+ try {
+ FileUtils.deleteDirectory(localDir)
+ } catch (e: IOException) {
+ e.printStackTrace()
+ MaterialAlertDialogBuilder(this).setMessage(e.message).show()
+ }
+
+ }
+ } catch (e: Exception) {
+ //This is what happens when jgit fails :(
+ //TODO Handle the diffent cases of exceptions
+ e.printStackTrace()
+ MaterialAlertDialogBuilder(this).setMessage(e.message).show()
+ }
+
+ launchGitOperation(REQUEST_CLONE)
+ }
+ }
+
+ /**
+ * Syncs the local repository with the remote one (either pull or push)
+ *
+ * @param operation the operation to execute can be REQUEST_PULL or REQUEST_PUSH
+ */
+ private fun syncRepository(operation: Int) {
+ if (settings.getString("git_remote_username", "")!!.isEmpty() ||
+ settings.getString("git_remote_server", "")!!.isEmpty() ||
+ settings.getString("git_remote_location", "")!!.isEmpty())
+ MaterialAlertDialogBuilder(this)
+ .setMessage(context.getString(R.string.set_information_dialog_text))
+ .setPositiveButton(context.getString(R.string.dialog_positive)) { _, _ ->
+ val intent = Intent(context, UserPreference::class.java)
+ startActivityForResult(intent, REQUEST_PULL)
+ }
+ .setNegativeButton(context.getString(R.string.dialog_negative)) { _, _ ->
+ // do nothing :(
+ setResult(AppCompatActivity.RESULT_OK)
+ finish()
+ }
+ .show()
+ else {
+ // check that the remote origin is here, else add it
+ PasswordRepository.addRemote("origin", hostname, false)
+ launchGitOperation(operation)
+ }
+ }
+
+ /**
+ * Attempt to launch the requested GIT operation. Depending on the configured auth, it may not
+ * be possible to launch the operation immediately. In that case, this function may launch an
+ * intermediate activity instead, which will gather necessary information and post it back via
+ * onActivityResult, which will then re-call this function. This may happen multiple times,
+ * until either an error is encountered or the operation is successfully launched.
+ *
+ * @param operation The type of GIT operation to launch
+ */
+ private fun launchGitOperation(operation: Int) {
+ val op: GitOperation
+ val localDir = requireNotNull(PasswordRepository.getRepositoryDirectory(context))
+
+ try {
+
+ // Before launching the operation with OpenKeychain auth, we need to issue several requests
+ // to the OpenKeychain API. IdentityBuild will take care of launching the relevant intents,
+ // we just need to keep calling it until it returns a completed ApiIdentity.
+ if (connectionMode.equals("OpenKeychain", ignoreCase = true) && identity == null) {
+ // Lazy initialization of the IdentityBuilder
+ if (identityBuilder == null) {
+ identityBuilder = SshApiSessionFactory.IdentityBuilder(this)
+ }
+
+ // Try to get an ApiIdentity and bail if one is not ready yet. The builder will ensure
+ // that onActivityResult is called with operation again, which will re-invoke us here
+ identity = identityBuilder!!.tryBuild(operation)
+ if (identity == null)
+ return
+ }
+
+ when (operation) {
+ REQUEST_CLONE, GitOperation.GET_SSH_KEY_FROM_CLONE -> op = CloneOperation(localDir, this).setCommand(hostname)
+
+ REQUEST_PULL -> op = PullOperation(localDir, this).setCommand()
+
+ REQUEST_PUSH -> op = PushOperation(localDir, this).setCommand()
+
+ REQUEST_SYNC -> op = SyncOperation(localDir, this).setCommands()
+
+ BREAK_OUT_OF_DETACHED -> op = BreakOutOfDetached(localDir, this).setCommands()
+
+ REQUEST_RESET -> op = ResetToRemoteOperation(localDir, this).setCommands()
+
+ SshApiSessionFactory.POST_SIGNATURE -> return
+
+ else -> {
+ Log.e(TAG, "Operation not recognized : $operation")
+ setResult(AppCompatActivity.RESULT_CANCELED)
+ finish()
+ return
+ }
+ }
+
+ op.executeAfterAuthentication(connectionMode,
+ settings.getString("git_remote_username", "git")!!,
+ File("$filesDir/.ssh_key"),
+ identity)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ MaterialAlertDialogBuilder(this).setMessage(e.message).show()
+ }
+
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int,
+ data: Intent?) {
+
+ // In addition to the pre-operation-launch series of intents for OpenKeychain auth
+ // that will pass through here and back to launchGitOperation, there is one
+ // synchronous operation that happens /after/ the operation has been launched in the
+ // background thread - the actual signing of the SSH challenge. We pass through the
+ // completed signature to the ApiIdentity, which will be blocked in the other thread
+ // waiting for it.
+ if (requestCode == SshApiSessionFactory.POST_SIGNATURE && identity != null)
+ identity!!.postSignature(data)
+
+ if (resultCode == AppCompatActivity.RESULT_CANCELED) {
+ setResult(AppCompatActivity.RESULT_CANCELED)
+ finish()
+ } else if (resultCode == AppCompatActivity.RESULT_OK) {
+ // If an operation has been re-queued via this mechanism, let the
+ // IdentityBuilder attempt to extract some updated state from the intent before
+ // trying to re-launch the operation.
+ if (identityBuilder != null) {
+ identityBuilder!!.consume(data)
+ }
+ launchGitOperation(requestCode)
+ }
+ super.onActivityResult(requestCode, resultCode, data)
+ }
+
+ companion object {
+ const val REQUEST_PULL = 101
+ const val REQUEST_PUSH = 102
+ const val REQUEST_CLONE = 103
+ const val REQUEST_INIT = 104
+ const val EDIT_SERVER = 105
+ const val REQUEST_SYNC = 106
+ @Suppress("Unused")
+ const val REQUEST_CREATE = 107
+ const val EDIT_GIT_CONFIG = 108
+ const val BREAK_OUT_OF_DETACHED = 109
+ const val REQUEST_RESET = 110
+ private const val TAG = "GitAct"
+ private const val emailPattern = "^[^@]+@[^@]+$"
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt
index 5c945c30..4ab90e96 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt
@@ -3,15 +3,14 @@ package com.zeapo.pwdstore.git
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
-import android.preference.PreferenceManager
import android.text.InputType
import android.view.LayoutInflater
import android.view.View
import android.widget.CheckBox
import android.widget.EditText
import android.widget.LinearLayout
-import androidx.appcompat.app.AlertDialog
-
+import androidx.preference.PreferenceManager
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jcraft.jsch.JSch
import com.jcraft.jsch.JSchException
import com.jcraft.jsch.KeyPair
@@ -21,12 +20,10 @@ import com.zeapo.pwdstore.git.config.GitConfigSessionFactory
import com.zeapo.pwdstore.git.config.SshApiSessionFactory
import com.zeapo.pwdstore.git.config.SshConfigSessionFactory
import com.zeapo.pwdstore.utils.PasswordRepository
-
import org.eclipse.jgit.api.GitCommand
import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.transport.SshSessionFactory
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider
-
import java.io.File
/**
@@ -37,7 +34,7 @@ import java.io.File
*/
abstract class GitOperation(fileDir: File, internal val callingActivity: Activity) {
- protected val repository: Repository = PasswordRepository.getRepository(fileDir)
+ protected val repository: Repository? = PasswordRepository.getRepository(fileDir)
internal var provider: UsernamePasswordCredentialsProvider? = null
internal var command: GitCommand<*>? = null
@@ -117,7 +114,7 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
showError: Boolean) {
if (connectionMode.equals("ssh-key", ignoreCase = true)) {
if (sshKey == null || !sshKey.exists()) {
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setMessage(callingActivity.resources.getString(R.string.ssh_preferences_dialog_text))
.setTitle(callingActivity.resources.getString(R.string.ssh_preferences_dialog_title))
.setPositiveButton(callingActivity.resources.getString(R.string.ssh_preferences_dialog_import)) { _, _ ->
@@ -170,7 +167,7 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
executeAfterAuthentication(connectionMode, username, sshKey, identity, true)
}
} else {
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.passphrase_dialog_title))
.setMessage(callingActivity.resources.getString(R.string.passphrase_dialog_text))
.setView(dialogView)
@@ -195,10 +192,11 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
setAuthentication(sshKey, username, "").execute()
}
} catch (e: JSchException) {
- AlertDialog.Builder(callingActivity)
+ e.printStackTrace()
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle("Unable to open the ssh-key")
.setMessage("Please check that it was imported.")
- .setPositiveButton("Ok") { _, _ -> }
+ .setPositiveButton("Ok") { _, _ -> callingActivity.finish() }
.show()
}
@@ -211,7 +209,7 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
password.width = LinearLayout.LayoutParams.MATCH_PARENT
password.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.passphrase_dialog_title))
.setMessage(callingActivity.resources.getString(R.string.password_dialog_text))
.setView(password)
@@ -231,7 +229,7 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
* Action to execute on error
*/
open fun onError(errorMessage: String) {
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage(callingActivity.resources.getString(R.string.jgit_error_dialog_text) + errorMessage)
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/PullOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/PullOperation.kt
index fa6c5445..90c46878 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/PullOperation.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/PullOperation.kt
@@ -1,11 +1,10 @@
package com.zeapo.pwdstore.git
import android.app.Activity
-import android.app.AlertDialog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.R
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.PullCommand
-
import java.io.File
/**
@@ -30,14 +29,12 @@ class PullOperation(fileDir: File, callingActivity: Activity) : GitOperation(fil
}
override fun execute() {
- if (this.provider != null) {
- (this.command as PullCommand).setCredentialsProvider(this.provider)
- }
+ (this.command as? PullCommand)?.setCredentialsProvider(this.provider)
GitAsyncTask(callingActivity, true, false, this).execute(this.command)
}
override fun onError(errorMessage: String) {
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage("Error occured during the pull operation, "
+ callingActivity.resources.getString(R.string.jgit_error_dialog_text)
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/PushOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/PushOperation.kt
index 9674e5b0..0015c4d1 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/PushOperation.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/PushOperation.kt
@@ -1,11 +1,10 @@
package com.zeapo.pwdstore.git
import android.app.Activity
-import android.app.AlertDialog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.R
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.PushCommand
-
import java.io.File
/**
@@ -30,15 +29,13 @@ class PushOperation(fileDir: File, callingActivity: Activity) : GitOperation(fil
}
override fun execute() {
- if (this.provider != null) {
- (this.command as PushCommand).setCredentialsProvider(this.provider)
- }
+ (this.command as? PushCommand)?.setCredentialsProvider(this.provider)
GitAsyncTask(callingActivity, true, false, this).execute(this.command)
}
override fun onError(errorMessage: String) {
// TODO handle the "Nothing to push" case
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage(callingActivity.getString(R.string.jgit_error_push_dialog_text) + errorMessage)
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> callingActivity.finish() }
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/ResetToRemoteOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/ResetToRemoteOperation.kt
new file mode 100644
index 00000000..1bab0981
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/git/ResetToRemoteOperation.kt
@@ -0,0 +1,52 @@
+package com.zeapo.pwdstore.git
+
+import android.app.Activity
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.zeapo.pwdstore.R
+import org.eclipse.jgit.api.AddCommand
+import org.eclipse.jgit.api.FetchCommand
+import org.eclipse.jgit.api.Git
+import org.eclipse.jgit.api.ResetCommand
+import java.io.File
+
+/**
+ * Creates a new git operation
+ *
+ * @param fileDir the git working tree directory
+ * @param callingActivity the calling activity
+ */
+class ResetToRemoteOperation(fileDir: File, callingActivity: Activity) : GitOperation(fileDir, callingActivity) {
+ private var addCommand: AddCommand? = null
+ private var fetchCommand: FetchCommand? = null
+ private var resetCommand: ResetCommand? = null
+
+ /**
+ * Sets the command
+ *
+ * @return the current object
+ */
+ fun setCommands(): ResetToRemoteOperation {
+ val git = Git(repository)
+ this.addCommand = git.add().addFilepattern(".")
+ this.fetchCommand = git.fetch().setRemote("origin")
+ this.resetCommand = git.reset().setRef("origin/master").setMode(ResetCommand.ResetType.HARD)
+ return this
+ }
+
+ override fun execute() {
+ this.fetchCommand?.setCredentialsProvider(this.provider)
+ GitAsyncTask(callingActivity, true, false, this)
+ .execute(this.addCommand, this.fetchCommand, this.resetCommand)
+ }
+
+ override fun onError(errorMessage: String) {
+ MaterialAlertDialogBuilder(callingActivity)
+ .setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
+ .setMessage("Error occured during the sync operation, "
+ + "\nPlease check the FAQ for possible reasons why this error might occur."
+ + callingActivity.resources.getString(R.string.jgit_error_dialog_text)
+ + errorMessage)
+ .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> }
+ .show()
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/SyncOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/SyncOperation.kt
index 8712a200..a0fd52b2 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/SyncOperation.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/SyncOperation.kt
@@ -1,7 +1,7 @@
package com.zeapo.pwdstore.git
import android.app.Activity
-import android.app.AlertDialog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.R
import org.eclipse.jgit.api.AddCommand
import org.eclipse.jgit.api.CommitCommand
@@ -9,7 +9,6 @@ import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.PullCommand
import org.eclipse.jgit.api.PushCommand
import org.eclipse.jgit.api.StatusCommand
-
import java.io.File
/**
@@ -42,14 +41,15 @@ class SyncOperation(fileDir: File, callingActivity: Activity) : GitOperation(fil
override fun execute() {
if (this.provider != null) {
- this.pullCommand!!.setCredentialsProvider(this.provider)
- this.pushCommand!!.setCredentialsProvider(this.provider)
+ this.pullCommand?.setCredentialsProvider(this.provider)
+ this.pushCommand?.setCredentialsProvider(this.provider)
}
GitAsyncTask(callingActivity, true, false, this).execute(this.addCommand, this.statusCommand, this.commitCommand, this.pullCommand, this.pushCommand)
}
override fun onError(errorMessage: String) {
- AlertDialog.Builder(callingActivity).setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
+ MaterialAlertDialogBuilder(callingActivity)
+ .setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage("Error occured during the sync operation, "
+ "\nPlease check the FAQ for possible reasons why this error might occur."
+ callingActivity.resources.getString(R.string.jgit_error_dialog_text)
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java b/app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java
index 085beadd..dbb5b7b1 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java
+++ b/app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java
@@ -5,8 +5,9 @@ import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender;
-import androidx.appcompat.app.AlertDialog;
+import androidx.annotation.NonNull;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.jcraft.jsch.Identity;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
@@ -49,9 +50,9 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
this.identity = identity;
}
+ @NonNull
@Override
- protected JSch
- getJSch(final OpenSshConfig.Host hc, FS fs) throws JSchException {
+ protected JSch getJSch(@NonNull final OpenSshConfig.Host hc, @NonNull FS fs) throws JSchException {
JSch jsch = super.getJSch(hc, fs);
jsch.removeAllIdentity();
jsch.addIdentity(identity, null);
@@ -59,7 +60,7 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
}
@Override
- protected void configure(OpenSshConfig.Host hc, Session session) {
+ protected void configure(@NonNull OpenSshConfig.Host hc, Session session) {
session.setConfig("StrictHostKeyChecking", "no");
session.setConfig("PreferredAuthentications", "publickey");
@@ -204,8 +205,9 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
@Override
public void onError() {
- new AlertDialog.Builder(callingActivity).setMessage(callingActivity.getString(
- R.string.openkeychain_ssh_api_connect_fail)).show();
+ new MaterialAlertDialogBuilder(callingActivity)
+ .setMessage(callingActivity.getString(
+ R.string.openkeychain_ssh_api_connect_fail)).show();
}
});