mirror of
https://github.com/android-password-store/Android-Password-Store
synced 2025-08-31 22:35:17 +00:00
Remove GitAsyncTask and replace with non-blocking coroutines (#865)
Co-authored-by: Fabian Henneke <fabian@henneke.me>
This commit is contained in:
@@ -23,6 +23,7 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere
|
|||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
instance = this
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
if (BuildConfig.ENABLE_DEBUG_FEATURES || prefs?.getBoolean(PreferenceKeys.ENABLE_DEBUG_LOGGING, false) ==
|
if (BuildConfig.ENABLE_DEBUG_FEATURES || prefs?.getBoolean(PreferenceKeys.ENABLE_DEBUG_LOGGING, false) ==
|
||||||
true) {
|
true) {
|
||||||
@@ -52,4 +53,9 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere
|
|||||||
else -> MODE_NIGHT_AUTO_BATTERY
|
else -> MODE_NIGHT_AUTO_BATTERY
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
lateinit var instance: Application
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,8 +18,6 @@ import androidx.activity.result.contract.ActivityResultContracts.StartActivityFo
|
|||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.fragment.app.setFragmentResultListener
|
|
||||||
import androidx.lifecycle.observe
|
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
@@ -34,7 +34,6 @@ import androidx.fragment.app.FragmentManager
|
|||||||
import androidx.fragment.app.commit
|
import androidx.fragment.app.commit
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.observe
|
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.github.ajalt.timberkt.d
|
import com.github.ajalt.timberkt.d
|
||||||
import com.github.ajalt.timberkt.e
|
import com.github.ajalt.timberkt.e
|
||||||
@@ -581,7 +580,12 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
|
|||||||
intent.putExtra("REPO_PATH", getRepositoryDirectory(applicationContext).absolutePath)
|
intent.putExtra("REPO_PATH", getRepositoryDirectory(applicationContext).absolutePath)
|
||||||
registerForActivityResult(StartActivityForResult()) { result ->
|
registerForActivityResult(StartActivityForResult()) { result ->
|
||||||
if (result.resultCode == RESULT_OK) {
|
if (result.resultCode == RESULT_OK) {
|
||||||
commitChange(resources.getString(R.string.git_commit_add_text, result.data?.extras?.getString("LONG_NAME")))
|
lifecycleScope.launch {
|
||||||
|
commitChange(
|
||||||
|
resources.getString(R.string.git_commit_add_text, result.data?.extras?.getString("LONG_NAME")),
|
||||||
|
finishActivityOnEnd = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
refreshPasswordList()
|
refreshPasswordList()
|
||||||
}
|
}
|
||||||
}.launch(intent)
|
}.launch(intent)
|
||||||
@@ -618,11 +622,15 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
|
|||||||
selectedItems.map { item -> item.file.deleteRecursively() }
|
selectedItems.map { item -> item.file.deleteRecursively() }
|
||||||
refreshPasswordList()
|
refreshPasswordList()
|
||||||
AutofillMatcher.updateMatches(applicationContext, delete = filesToDelete)
|
AutofillMatcher.updateMatches(applicationContext, delete = filesToDelete)
|
||||||
commitChange(resources.getString(R.string.git_commit_remove_text,
|
val fmt = selectedItems.joinToString(separator = ", ") { item ->
|
||||||
selectedItems.joinToString(separator = ", ") { item ->
|
item.file.toRelativeString(getRepositoryDirectory(this@PasswordStore))
|
||||||
item.file.toRelativeString(getRepositoryDirectory(this))
|
}
|
||||||
}
|
lifecycleScope.launch {
|
||||||
))
|
commitChange(
|
||||||
|
resources.getString(R.string.git_commit_remove_text, fmt),
|
||||||
|
finishActivityOnEnd = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(resources.getString(R.string.dialog_no), null)
|
.setNegativeButton(resources.getString(R.string.dialog_no), null)
|
||||||
.show()
|
.show()
|
||||||
@@ -688,14 +696,20 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
|
|||||||
val sourceLongName = getLongName(requireNotNull(source.parent), repositoryPath, basename)
|
val sourceLongName = getLongName(requireNotNull(source.parent), repositoryPath, basename)
|
||||||
val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename)
|
val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
commitChange(resources.getString(R.string.git_commit_move_text, sourceLongName, destinationLongName))
|
commitChange(
|
||||||
|
resources.getString(R.string.git_commit_move_text, sourceLongName, destinationLongName),
|
||||||
|
finishActivityOnEnd = false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
val repoDir = getRepositoryDirectory(applicationContext).absolutePath
|
||||||
|
val relativePath = getRelativePath("${target.absolutePath}/", repoDir)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
commitChange(resources.getString(R.string.git_commit_move_multiple_text,
|
commitChange(
|
||||||
getRelativePath("${target.absolutePath}/", getRepositoryDirectory(applicationContext).absolutePath)
|
resources.getString(R.string.git_commit_move_multiple_text, relativePath),
|
||||||
))
|
finishActivityOnEnd = false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -746,7 +760,10 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
|
|||||||
else -> lifecycleScope.launch(Dispatchers.IO) {
|
else -> lifecycleScope.launch(Dispatchers.IO) {
|
||||||
moveFile(oldCategory.file, newCategory)
|
moveFile(oldCategory.file, newCategory)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
commitChange(resources.getString(R.string.git_commit_move_text, oldCategory.name, newCategory.name))
|
commitChange(
|
||||||
|
resources.getString(R.string.git_commit_move_text, oldCategory.name, newCategory.name),
|
||||||
|
finishActivityOnEnd = false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,7 +10,6 @@ import android.view.View
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.observe
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.zeapo.pwdstore.databinding.PasswordRecyclerViewBinding
|
import com.zeapo.pwdstore.databinding.PasswordRecyclerViewBinding
|
||||||
import com.zeapo.pwdstore.ui.adapters.PasswordItemRecyclerAdapter
|
import com.zeapo.pwdstore.ui.adapters.PasswordItemRecyclerAdapter
|
||||||
|
@@ -22,7 +22,6 @@ import androidx.core.text.buildSpannedString
|
|||||||
import androidx.core.text.underline
|
import androidx.core.text.underline
|
||||||
import androidx.core.widget.addTextChangedListener
|
import androidx.core.widget.addTextChangedListener
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.observe
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.github.ajalt.timberkt.e
|
import com.github.ajalt.timberkt.e
|
||||||
import com.zeapo.pwdstore.FilterMode
|
import com.zeapo.pwdstore.FilterMode
|
||||||
|
@@ -15,6 +15,7 @@ import androidx.activity.result.contract.ActivityResultContracts.StartActivityFo
|
|||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.github.ajalt.timberkt.e
|
import com.github.ajalt.timberkt.e
|
||||||
import com.zeapo.pwdstore.R
|
import com.zeapo.pwdstore.R
|
||||||
import com.zeapo.pwdstore.autofill.oreo.AutofillAction
|
import com.zeapo.pwdstore.autofill.oreo.AutofillAction
|
||||||
@@ -27,6 +28,7 @@ import com.zeapo.pwdstore.crypto.PasswordCreationActivity
|
|||||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||||
import com.zeapo.pwdstore.utils.commitChange
|
import com.zeapo.pwdstore.utils.commitChange
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
class AutofillSaveActivity : AppCompatActivity() {
|
class AutofillSaveActivity : AppCompatActivity() {
|
||||||
@@ -144,10 +146,12 @@ class AutofillSaveActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
// PasswordCreationActivity delegates committing the added file to PasswordStore. Since
|
// PasswordCreationActivity delegates committing the added file to PasswordStore. Since
|
||||||
// PasswordStore is not involved in an AutofillScenario, we have to commit the file ourselves.
|
// PasswordStore is not involved in an AutofillScenario, we have to commit the file ourselves.
|
||||||
commitChange(
|
lifecycleScope.launch {
|
||||||
getString(R.string.git_commit_add_text, longName),
|
commitChange(
|
||||||
finishWithResultOnEnd = resultIntent
|
getString(R.string.git_commit_add_text, longName),
|
||||||
)
|
finishWithResultOnEnd = resultIntent
|
||||||
|
)
|
||||||
|
}
|
||||||
// GitAsyncTask will finish the activity for us.
|
// GitAsyncTask will finish the activity for us.
|
||||||
}
|
}
|
||||||
}.launch(saveIntent)
|
}.launch(saveIntent)
|
||||||
|
@@ -329,12 +329,14 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
|
|||||||
gpgIdentifierFile.writeText(keyIds.joinToString("\n"))
|
gpgIdentifierFile.writeText(keyIds.joinToString("\n"))
|
||||||
val repo = PasswordRepository.getRepository(null)
|
val repo = PasswordRepository.getRepository(null)
|
||||||
if (repo != null) {
|
if (repo != null) {
|
||||||
commitChange(
|
lifecycleScope.launch {
|
||||||
getString(
|
commitChange(
|
||||||
R.string.git_commit_gpg_id,
|
getString(
|
||||||
getLongName(gpgIdentifierFile.parentFile!!.absolutePath, repoPath, gpgIdentifierFile.name)
|
R.string.git_commit_gpg_id,
|
||||||
|
getLongName(gpgIdentifierFile.parentFile!!.absolutePath, repoPath, gpgIdentifierFile.name)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
encrypt(data)
|
encrypt(data)
|
||||||
}
|
}
|
||||||
@@ -422,7 +424,8 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
|
|||||||
AutofillPreferences.directoryStructure(applicationContext)
|
AutofillPreferences.directoryStructure(applicationContext)
|
||||||
val entry = PasswordEntry(content)
|
val entry = PasswordEntry(content)
|
||||||
returnIntent.putExtra(RETURN_EXTRA_PASSWORD, entry.password)
|
returnIntent.putExtra(RETURN_EXTRA_PASSWORD, entry.password)
|
||||||
val username = entry.username ?: directoryStructure.getUsernameFor(file)
|
val username = entry.username
|
||||||
|
?: directoryStructure.getUsernameFor(file)
|
||||||
returnIntent.putExtra(RETURN_EXTRA_USERNAME, username)
|
returnIntent.putExtra(RETURN_EXTRA_USERNAME, username)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,12 +433,14 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
|
|||||||
if (repo != null) {
|
if (repo != null) {
|
||||||
val status = Git(repo).status().call()
|
val status = Git(repo).status().call()
|
||||||
if (status.modified.isNotEmpty()) {
|
if (status.modified.isNotEmpty()) {
|
||||||
commitChange(
|
lifecycleScope.launch {
|
||||||
getString(
|
commitChange(
|
||||||
R.string.git_commit_edit_text,
|
getString(
|
||||||
getLongName(fullPath, repoPath, editName)
|
R.string.git_commit_edit_text,
|
||||||
|
getLongName(fullPath, repoPath, editName)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,6 +12,7 @@ import androidx.annotation.CallSuper
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.core.text.isDigitsOnly
|
import androidx.core.text.isDigitsOnly
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.github.ajalt.timberkt.Timber.tag
|
import com.github.ajalt.timberkt.Timber.tag
|
||||||
import com.github.ajalt.timberkt.e
|
import com.github.ajalt.timberkt.e
|
||||||
@@ -20,11 +21,19 @@ import com.zeapo.pwdstore.R
|
|||||||
import com.zeapo.pwdstore.git.config.ConnectionMode
|
import com.zeapo.pwdstore.git.config.ConnectionMode
|
||||||
import com.zeapo.pwdstore.git.config.Protocol
|
import com.zeapo.pwdstore.git.config.Protocol
|
||||||
import com.zeapo.pwdstore.git.config.SshApiSessionFactory
|
import com.zeapo.pwdstore.git.config.SshApiSessionFactory
|
||||||
|
import com.zeapo.pwdstore.git.operation.BreakOutOfDetached
|
||||||
|
import com.zeapo.pwdstore.git.operation.CloneOperation
|
||||||
|
import com.zeapo.pwdstore.git.operation.GitOperation
|
||||||
|
import com.zeapo.pwdstore.git.operation.PullOperation
|
||||||
|
import com.zeapo.pwdstore.git.operation.PushOperation
|
||||||
|
import com.zeapo.pwdstore.git.operation.ResetToRemoteOperation
|
||||||
|
import com.zeapo.pwdstore.git.operation.SyncOperation
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||||
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract AppCompatActivity that holds some information that is commonly shared across git-related
|
* Abstract AppCompatActivity that holds some information that is commonly shared across git-related
|
||||||
@@ -166,7 +175,7 @@ abstract class BaseGitActivity : AppCompatActivity() {
|
|||||||
*
|
*
|
||||||
* @param operation The type of git operation to launch
|
* @param operation The type of git operation to launch
|
||||||
*/
|
*/
|
||||||
fun launchGitOperation(operation: Int) {
|
suspend fun launchGitOperation(operation: Int) {
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
setResult(RESULT_CANCELED)
|
setResult(RESULT_CANCELED)
|
||||||
finish()
|
finish()
|
||||||
@@ -190,12 +199,12 @@ abstract class BaseGitActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
val localDir = requireNotNull(PasswordRepository.getRepositoryDirectory(this))
|
val localDir = requireNotNull(PasswordRepository.getRepositoryDirectory(this))
|
||||||
val op = when (operation) {
|
val op = when (operation) {
|
||||||
REQUEST_CLONE, GitOperation.GET_SSH_KEY_FROM_CLONE -> CloneOperation(localDir, this).setCommand(url!!)
|
REQUEST_CLONE, GitOperation.GET_SSH_KEY_FROM_CLONE -> CloneOperation(localDir, url!!, this)
|
||||||
REQUEST_PULL -> PullOperation(localDir, this).setCommand()
|
REQUEST_PULL -> PullOperation(localDir, this)
|
||||||
REQUEST_PUSH -> PushOperation(localDir, this).setCommand()
|
REQUEST_PUSH -> PushOperation(localDir, this)
|
||||||
REQUEST_SYNC -> SyncOperation(localDir, this).setCommands()
|
REQUEST_SYNC -> SyncOperation(localDir, this)
|
||||||
BREAK_OUT_OF_DETACHED -> BreakOutOfDetached(localDir, this).setCommands()
|
BREAK_OUT_OF_DETACHED -> BreakOutOfDetached(localDir, this)
|
||||||
REQUEST_RESET -> ResetToRemoteOperation(localDir, this).setCommands()
|
REQUEST_RESET -> ResetToRemoteOperation(localDir, this)
|
||||||
SshApiSessionFactory.POST_SIGNATURE -> return
|
SshApiSessionFactory.POST_SIGNATURE -> return
|
||||||
else -> {
|
else -> {
|
||||||
tag(TAG).e { "Operation not recognized : $operation" }
|
tag(TAG).e { "Operation not recognized : $operation" }
|
||||||
@@ -239,7 +248,7 @@ abstract class BaseGitActivity : AppCompatActivity() {
|
|||||||
if (identityBuilder != null) {
|
if (identityBuilder != null) {
|
||||||
identityBuilder!!.consume(data)
|
identityBuilder!!.consume(data)
|
||||||
}
|
}
|
||||||
launchGitOperation(requestCode)
|
lifecycleScope.launch { launchGitOperation(requestCode) }
|
||||||
}
|
}
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
}
|
}
|
||||||
|
@@ -1,90 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
package com.zeapo.pwdstore.git
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.zeapo.pwdstore.R
|
|
||||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
|
||||||
import java.io.File
|
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import org.eclipse.jgit.api.GitCommand
|
|
||||||
import org.eclipse.jgit.api.PushCommand
|
|
||||||
import org.eclipse.jgit.api.RebaseCommand
|
|
||||||
|
|
||||||
class BreakOutOfDetached(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
|
|
||||||
|
|
||||||
private lateinit var commands: List<GitCommand<out Any>>
|
|
||||||
private val gitBranch = PreferenceManager
|
|
||||||
.getDefaultSharedPreferences(callingActivity.applicationContext)
|
|
||||||
.getString(PreferenceKeys.GIT_BRANCH_NAME, "master")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the command
|
|
||||||
*
|
|
||||||
* @return the current object
|
|
||||||
*/
|
|
||||||
fun setCommands(): BreakOutOfDetached {
|
|
||||||
val git = Git(repository)
|
|
||||||
val branchName = "conflicting-$gitBranch-${System.currentTimeMillis()}"
|
|
||||||
|
|
||||||
this.commands = listOf(
|
|
||||||
// abort the rebase
|
|
||||||
git.rebase().setOperation(RebaseCommand.Operation.ABORT),
|
|
||||||
// git checkout -b conflict-branch
|
|
||||||
git.checkout().setCreateBranch(true).setName(branchName),
|
|
||||||
// push the changes
|
|
||||||
git.push().setRemote("origin"),
|
|
||||||
// switch back to ${gitBranch}
|
|
||||||
git.checkout().setName(gitBranch)
|
|
||||||
)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun execute() {
|
|
||||||
val git = Git(repository)
|
|
||||||
if (!git.repository.repositoryState.isRebasing) {
|
|
||||||
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)) { _, _ ->
|
|
||||||
callingActivity.finish()
|
|
||||||
}.show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.provider != null) {
|
|
||||||
// set the credentials for push command
|
|
||||||
this.commands.forEach { cmd ->
|
|
||||||
if (cmd is PushCommand) {
|
|
||||||
cmd.setCredentialsProvider(this.provider)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GitAsyncTask(callingActivity, this, null)
|
|
||||||
.execute(*this.commands.toTypedArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(err: Exception) {
|
|
||||||
MaterialAlertDialogBuilder(callingActivity)
|
|
||||||
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
|
|
||||||
.setMessage("Error occurred when checking out another branch operation ${err.message}")
|
|
||||||
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
|
|
||||||
callingActivity.finish()
|
|
||||||
}.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSuccess() {
|
|
||||||
MaterialAlertDialogBuilder(callingActivity)
|
|
||||||
.setTitle(callingActivity.resources.getString(R.string.git_abort_and_push_title))
|
|
||||||
.setMessage("There was a conflict when trying to rebase. " +
|
|
||||||
"Your local $gitBranch branch was pushed to another branch named conflicting-$gitBranch-....\n" +
|
|
||||||
"Use this branch to resolve conflict on your computer")
|
|
||||||
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
|
|
||||||
callingActivity.finish()
|
|
||||||
}.show()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
package com.zeapo.pwdstore.git
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.zeapo.pwdstore.R
|
|
||||||
import java.io.File
|
|
||||||
import org.eclipse.jgit.api.CloneCommand
|
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new clone operation
|
|
||||||
*
|
|
||||||
* @param fileDir the git working tree directory
|
|
||||||
* @param callingActivity the calling activity
|
|
||||||
*/
|
|
||||||
class CloneOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the command using the repository uri
|
|
||||||
*
|
|
||||||
* @param uri the uri of the repository
|
|
||||||
* @return the current object
|
|
||||||
*/
|
|
||||||
fun setCommand(uri: String): CloneOperation {
|
|
||||||
this.command = Git.cloneRepository()
|
|
||||||
.setCloneAllBranches(true)
|
|
||||||
.setDirectory(repository?.workTree)
|
|
||||||
.setURI(uri)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun execute() {
|
|
||||||
(this.command as? CloneCommand)?.setCredentialsProvider(this.provider)
|
|
||||||
GitAsyncTask(callingActivity, this, Intent()).execute(this.command)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(err: Exception) {
|
|
||||||
super.onError(err)
|
|
||||||
MaterialAlertDialogBuilder(callingActivity)
|
|
||||||
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
|
|
||||||
.setMessage("Error occurred during the clone operation, " +
|
|
||||||
callingActivity.resources.getString(R.string.jgit_error_dialog_text) +
|
|
||||||
err.message +
|
|
||||||
"\nPlease check the FAQ for possible reasons why this error might occur.")
|
|
||||||
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> }
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
75
app/src/main/java/com/zeapo/pwdstore/git/ErrorMessages.kt
Normal file
75
app/src/main/java/com/zeapo/pwdstore/git/ErrorMessages.kt
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.zeapo.pwdstore.git
|
||||||
|
|
||||||
|
import android.os.RemoteException
|
||||||
|
import com.zeapo.pwdstore.Application
|
||||||
|
import com.zeapo.pwdstore.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supertype for all Git-related [Exception]s that can be thrown by [GitCommandExecutor.execute].
|
||||||
|
*/
|
||||||
|
sealed class GitException(message: String? = null) : Exception(message) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates possible errors from a [org.eclipse.jgit.api.PullCommand].
|
||||||
|
*/
|
||||||
|
class PullException(val reason: Reason) : GitException() {
|
||||||
|
|
||||||
|
enum class Reason {
|
||||||
|
REBASE_FAILED,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates possible errors from a [org.eclipse.jgit.api.PushCommand].
|
||||||
|
*/
|
||||||
|
class PushException(val reason: Reason, vararg val fmt: String) : GitException() {
|
||||||
|
enum class Reason {
|
||||||
|
NON_FAST_FORWARD,
|
||||||
|
REMOTE_REJECTED,
|
||||||
|
GENERIC,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ErrorMessages {
|
||||||
|
|
||||||
|
private val PULL_REASON_MAP = mapOf(
|
||||||
|
GitException.PullException.Reason.REBASE_FAILED to R.string.git_pull_fail_error,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val PUSH_REASON_MAP = mapOf(
|
||||||
|
GitException.PushException.Reason.NON_FAST_FORWARD to R.string.git_push_nff_error,
|
||||||
|
GitException.PushException.Reason.REMOTE_REJECTED to R.string.git_push_other_error,
|
||||||
|
GitException.PushException.Reason.GENERIC to R.string.git_push_generic_error,
|
||||||
|
)
|
||||||
|
|
||||||
|
operator fun get(throwable: Throwable?): String {
|
||||||
|
val resources = Application.instance.resources
|
||||||
|
if (throwable == null) return resources.getString(R.string.git_unknown_error)
|
||||||
|
return when (val rootCause = rootCause(throwable)) {
|
||||||
|
is GitException.PullException -> {
|
||||||
|
resources.getString(PULL_REASON_MAP.getValue(rootCause.reason))
|
||||||
|
}
|
||||||
|
is GitException.PushException -> {
|
||||||
|
resources.getString(PUSH_REASON_MAP.getValue(rootCause.reason), *rootCause.fmt)
|
||||||
|
}
|
||||||
|
else -> throwable.message ?: resources.getString(R.string.git_unknown_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rootCause(throwable: Throwable): Throwable {
|
||||||
|
var cause = throwable
|
||||||
|
while (cause.cause != null) {
|
||||||
|
if (cause is GitException) break
|
||||||
|
val nextCause = cause.cause!!
|
||||||
|
if (nextCause is RemoteException) break
|
||||||
|
cause = nextCause
|
||||||
|
}
|
||||||
|
return cause
|
||||||
|
}
|
||||||
|
}
|
@@ -2,23 +2,27 @@
|
|||||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.zeapo.pwdstore.git
|
package com.zeapo.pwdstore.git
|
||||||
|
|
||||||
import android.app.ProgressDialog
|
import android.app.Activity
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.AsyncTask
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import com.github.ajalt.timberkt.e
|
import com.github.ajalt.timberkt.e
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.zeapo.pwdstore.R
|
import com.zeapo.pwdstore.R
|
||||||
|
import com.zeapo.pwdstore.git.GitException.PullException
|
||||||
|
import com.zeapo.pwdstore.git.GitException.PushException
|
||||||
import com.zeapo.pwdstore.git.config.SshjSessionFactory
|
import com.zeapo.pwdstore.git.config.SshjSessionFactory
|
||||||
import java.io.IOException
|
import com.zeapo.pwdstore.git.operation.GitOperation
|
||||||
import java.lang.ref.WeakReference
|
import com.zeapo.pwdstore.utils.Result
|
||||||
|
import com.zeapo.pwdstore.utils.snackbar
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import net.schmizz.sshj.common.DisconnectReason
|
import net.schmizz.sshj.common.DisconnectReason
|
||||||
import net.schmizz.sshj.common.SSHException
|
import net.schmizz.sshj.common.SSHException
|
||||||
import net.schmizz.sshj.userauth.UserAuthException
|
import net.schmizz.sshj.userauth.UserAuthException
|
||||||
import org.eclipse.jgit.api.CommitCommand
|
import org.eclipse.jgit.api.CommitCommand
|
||||||
import org.eclipse.jgit.api.GitCommand
|
|
||||||
import org.eclipse.jgit.api.PullCommand
|
import org.eclipse.jgit.api.PullCommand
|
||||||
import org.eclipse.jgit.api.PushCommand
|
import org.eclipse.jgit.api.PushCommand
|
||||||
import org.eclipse.jgit.api.RebaseResult
|
import org.eclipse.jgit.api.RebaseResult
|
||||||
@@ -26,95 +30,122 @@ import org.eclipse.jgit.api.StatusCommand
|
|||||||
import org.eclipse.jgit.transport.RemoteRefUpdate
|
import org.eclipse.jgit.transport.RemoteRefUpdate
|
||||||
import org.eclipse.jgit.transport.SshSessionFactory
|
import org.eclipse.jgit.transport.SshSessionFactory
|
||||||
|
|
||||||
|
class GitCommandExecutor(
|
||||||
class GitAsyncTask(
|
private val activity: FragmentActivity,
|
||||||
activity: AppCompatActivity,
|
|
||||||
private val operation: GitOperation,
|
private val operation: GitOperation,
|
||||||
private val finishWithResultOnEnd: Intent?,
|
private val finishWithResultOnEnd: Intent? = Intent(),
|
||||||
private val silentlyExecute: Boolean = false
|
private val finishActivityOnEnd: Boolean = true,
|
||||||
) : AsyncTask<GitCommand<*>, Int, GitAsyncTask.Result>() {
|
) {
|
||||||
|
|
||||||
private val activityWeakReference: WeakReference<AppCompatActivity> = WeakReference(activity)
|
suspend fun execute() {
|
||||||
private val activity: AppCompatActivity?
|
operation.setCredentialProvider()
|
||||||
get() = activityWeakReference.get()
|
val snackbar = activity.snackbar(
|
||||||
private val context: Context = activity.applicationContext
|
message = activity.resources.getString(R.string.git_operation_running),
|
||||||
private val dialog = ProgressDialog(activity)
|
length = Snackbar.LENGTH_INDEFINITE,
|
||||||
|
)
|
||||||
sealed class Result {
|
var nbChanges = 0
|
||||||
object Ok : Result()
|
var operationResult: Result = Result.Ok
|
||||||
data class Err(val err: Exception) : Result()
|
for (command in operation.commands) {
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPreExecute() {
|
|
||||||
if (silentlyExecute) return
|
|
||||||
dialog.run {
|
|
||||||
setMessage(activity!!.resources.getString(R.string.running_dialog_text))
|
|
||||||
setCancelable(false)
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun doInBackground(vararg commands: GitCommand<*>): Result? {
|
|
||||||
var nbChanges: Int? = null
|
|
||||||
for (command in commands) {
|
|
||||||
try {
|
try {
|
||||||
when (command) {
|
when (command) {
|
||||||
is StatusCommand -> {
|
is StatusCommand -> {
|
||||||
// in case we have changes, we want to keep track of it
|
// in case we have changes, we want to keep track of it
|
||||||
val status = command.call()
|
val status = withContext(Dispatchers.IO) {
|
||||||
|
command.call()
|
||||||
|
}
|
||||||
nbChanges = status.changed.size + status.missing.size
|
nbChanges = status.changed.size + status.missing.size
|
||||||
}
|
}
|
||||||
is CommitCommand -> {
|
is CommitCommand -> {
|
||||||
// the previous status will eventually be used to avoid a commit
|
// the previous status will eventually be used to avoid a commit
|
||||||
if (nbChanges == null || nbChanges > 0) command.call()
|
withContext(Dispatchers.IO) {
|
||||||
|
if (nbChanges > 0) command.call()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is PullCommand -> {
|
is PullCommand -> {
|
||||||
val result = command.call()
|
val result = withContext(Dispatchers.IO) {
|
||||||
|
command.call()
|
||||||
|
}
|
||||||
val rr = result.rebaseResult
|
val rr = result.rebaseResult
|
||||||
if (rr.status === RebaseResult.Status.STOPPED) {
|
if (rr.status === RebaseResult.Status.STOPPED) {
|
||||||
return Result.Err(IOException(context.getString(R.string
|
operationResult = Result.Err(PullException(PullException.Reason.REBASE_FAILED))
|
||||||
.git_pull_fail_error)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is PushCommand -> {
|
is PushCommand -> {
|
||||||
for (result in command.call()) {
|
val results = withContext(Dispatchers.IO) {
|
||||||
|
command.call()
|
||||||
|
}
|
||||||
|
for (result in results) {
|
||||||
// Code imported (modified) from Gerrit PushOp, license Apache v2
|
// Code imported (modified) from Gerrit PushOp, license Apache v2
|
||||||
for (rru in result.remoteUpdates) {
|
for (rru in result.remoteUpdates) {
|
||||||
val error = when (rru.status) {
|
val error = when (rru.status) {
|
||||||
RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD ->
|
RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD -> {
|
||||||
context.getString(R.string.git_push_nff_error)
|
PushException(PushException.Reason.NON_FAST_FORWARD)
|
||||||
|
}
|
||||||
RemoteRefUpdate.Status.REJECTED_NODELETE,
|
RemoteRefUpdate.Status.REJECTED_NODELETE,
|
||||||
RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
|
RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
|
||||||
RemoteRefUpdate.Status.NON_EXISTING,
|
RemoteRefUpdate.Status.NON_EXISTING,
|
||||||
RemoteRefUpdate.Status.NOT_ATTEMPTED
|
RemoteRefUpdate.Status.NOT_ATTEMPTED,
|
||||||
->
|
-> {
|
||||||
(activity!!.getString(R.string.git_push_generic_error) + rru.status.name)
|
PushException(PushException.Reason.GENERIC, rru.status.name)
|
||||||
|
}
|
||||||
RemoteRefUpdate.Status.REJECTED_OTHER_REASON -> {
|
RemoteRefUpdate.Status.REJECTED_OTHER_REASON -> {
|
||||||
if
|
if ("non-fast-forward" == rru.message) {
|
||||||
("non-fast-forward" == rru.message) {
|
PushException(PushException.Reason.REMOTE_REJECTED)
|
||||||
context.getString(R.string.git_push_other_error)
|
|
||||||
} else {
|
} else {
|
||||||
(context.getString(R.string.git_push_generic_error)
|
PushException(PushException.Reason.GENERIC, rru.message)
|
||||||
+ rru.message)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> null
|
else -> null
|
||||||
|
|
||||||
}
|
}
|
||||||
if (error != null)
|
if (error != null) {
|
||||||
Result.Err(IOException(error))
|
operationResult = Result.Err(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
command.call()
|
withContext(Dispatchers.IO) {
|
||||||
|
command.call()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
return Result.Err(e)
|
operationResult = Result.Err(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Result.Ok
|
when (operationResult) {
|
||||||
|
is Result.Err -> {
|
||||||
|
activity.setResult(Activity.RESULT_CANCELED)
|
||||||
|
if (isExplicitlyUserInitiatedError(operationResult.err)) {
|
||||||
|
// Currently, this is only executed when the user cancels a password prompt
|
||||||
|
// during authentication.
|
||||||
|
if (finishActivityOnEnd) activity.finish()
|
||||||
|
} else {
|
||||||
|
e(operationResult.err)
|
||||||
|
operation.onError(rootCauseException(operationResult.err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Result.Ok -> {
|
||||||
|
operation.onSuccess()
|
||||||
|
activity.setResult(Activity.RESULT_OK, finishWithResultOnEnd)
|
||||||
|
if (finishActivityOnEnd) activity.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
snackbar.dismiss()
|
||||||
|
(SshSessionFactory.getInstance() as? SshjSessionFactory)?.clearCredentials()
|
||||||
|
SshSessionFactory.setInstance(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isExplicitlyUserInitiatedError(e: Exception): Boolean {
|
||||||
|
var cause: Exception? = e
|
||||||
|
while (cause != null) {
|
||||||
|
if (cause is SSHException &&
|
||||||
|
cause.disconnectReason == DisconnectReason.AUTH_CANCELLED_BY_USER)
|
||||||
|
return true
|
||||||
|
cause = cause.cause as? Exception
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun rootCauseException(e: Exception): Exception {
|
private fun rootCauseException(e: Exception): Exception {
|
||||||
@@ -130,47 +161,4 @@ class GitAsyncTask(
|
|||||||
}
|
}
|
||||||
return rootCause
|
return rootCause
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isExplicitlyUserInitiatedError(e: Exception): Boolean {
|
|
||||||
var cause: Exception? = e
|
|
||||||
while (cause != null) {
|
|
||||||
if (cause is SSHException &&
|
|
||||||
cause.disconnectReason == DisconnectReason.AUTH_CANCELLED_BY_USER)
|
|
||||||
return true
|
|
||||||
cause = cause.cause as? Exception
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPostExecute(maybeResult: Result?) {
|
|
||||||
if (!silentlyExecute) dialog.dismiss()
|
|
||||||
when (val result = maybeResult ?: Result.Err(IOException("Unexpected error"))) {
|
|
||||||
is Result.Err -> {
|
|
||||||
if (isExplicitlyUserInitiatedError(result.err)) {
|
|
||||||
// Currently, this is only executed when the user cancels a password prompt
|
|
||||||
// during authentication.
|
|
||||||
if (finishWithResultOnEnd != null) {
|
|
||||||
activity?.setResult(AppCompatActivity.RESULT_CANCELED)
|
|
||||||
activity?.finish()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
e(result.err)
|
|
||||||
operation.onError(rootCauseException(result.err))
|
|
||||||
if (finishWithResultOnEnd != null) {
|
|
||||||
activity?.setResult(AppCompatActivity.RESULT_CANCELED)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Result.Ok -> {
|
|
||||||
operation.onSuccess()
|
|
||||||
if (finishWithResultOnEnd != null) {
|
|
||||||
activity?.setResult(AppCompatActivity.RESULT_OK, finishWithResultOnEnd)
|
|
||||||
activity?.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(SshSessionFactory.getInstance() as? SshjSessionFactory)?.clearCredentials()
|
|
||||||
SshSessionFactory.setInstance(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@@ -9,6 +9,7 @@ import android.os.Handler
|
|||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.core.os.postDelayed
|
import androidx.core.os.postDelayed
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.zeapo.pwdstore.R
|
import com.zeapo.pwdstore.R
|
||||||
@@ -16,6 +17,7 @@ import com.zeapo.pwdstore.databinding.ActivityGitConfigBinding
|
|||||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||||
import com.zeapo.pwdstore.utils.viewBinding
|
import com.zeapo.pwdstore.utils.viewBinding
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.eclipse.jgit.lib.Constants
|
import org.eclipse.jgit.lib.Constants
|
||||||
|
|
||||||
class GitConfigActivity : BaseGitActivity() {
|
class GitConfigActivity : BaseGitActivity() {
|
||||||
@@ -47,8 +49,8 @@ class GitConfigActivity : BaseGitActivity() {
|
|||||||
} catch (ignored: Exception) {
|
} catch (ignored: Exception) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.gitAbortRebase.setOnClickListener { launchGitOperation(BREAK_OUT_OF_DETACHED) }
|
binding.gitAbortRebase.setOnClickListener { lifecycleScope.launch { launchGitOperation(BREAK_OUT_OF_DETACHED) } }
|
||||||
binding.gitResetToRemote.setOnClickListener { launchGitOperation(REQUEST_RESET) }
|
binding.gitResetToRemote.setOnClickListener { lifecycleScope.launch { launchGitOperation(REQUEST_RESET) } }
|
||||||
binding.saveButton.setOnClickListener {
|
binding.saveButton.setOnClickListener {
|
||||||
val email = binding.gitUserEmail.text.toString().trim()
|
val email = binding.gitUserEmail.text.toString().trim()
|
||||||
val name = binding.gitUserName.text.toString().trim()
|
val name = binding.gitUserName.text.toString().trim()
|
||||||
|
@@ -8,19 +8,21 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.zeapo.pwdstore.R
|
import com.zeapo.pwdstore.R
|
||||||
import com.zeapo.pwdstore.UserPreference
|
import com.zeapo.pwdstore.UserPreference
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
open class GitOperationActivity : BaseGitActivity() {
|
open class GitOperationActivity : BaseGitActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
when (intent.extras?.getInt(REQUEST_ARG_OP)) {
|
when (intent.extras?.getInt(REQUEST_ARG_OP)) {
|
||||||
REQUEST_PULL -> syncRepository(REQUEST_PULL)
|
REQUEST_PULL -> lifecycleScope.launch { syncRepository(REQUEST_PULL) }
|
||||||
REQUEST_PUSH -> syncRepository(REQUEST_PUSH)
|
REQUEST_PUSH -> lifecycleScope.launch { syncRepository(REQUEST_PUSH) }
|
||||||
REQUEST_SYNC -> syncRepository(REQUEST_SYNC)
|
REQUEST_SYNC -> lifecycleScope.launch { syncRepository(REQUEST_SYNC) }
|
||||||
else -> {
|
else -> {
|
||||||
setResult(RESULT_CANCELED)
|
setResult(RESULT_CANCELED)
|
||||||
finish()
|
finish()
|
||||||
@@ -54,7 +56,7 @@ open class GitOperationActivity : BaseGitActivity() {
|
|||||||
*
|
*
|
||||||
* @param operation the operation to execute can be REQUEST_PULL or REQUEST_PUSH
|
* @param operation the operation to execute can be REQUEST_PULL or REQUEST_PUSH
|
||||||
*/
|
*/
|
||||||
private fun syncRepository(operation: Int) {
|
private suspend fun syncRepository(operation: Int) {
|
||||||
if (serverUser.isEmpty() || serverHostname.isEmpty() || url.isNullOrEmpty())
|
if (serverUser.isEmpty() || serverHostname.isEmpty() || url.isNullOrEmpty())
|
||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setMessage(getString(R.string.set_information_dialog_text))
|
.setMessage(getString(R.string.set_information_dialog_text))
|
||||||
|
@@ -10,6 +10,7 @@ import android.view.View
|
|||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.core.os.postDelayed
|
import androidx.core.os.postDelayed
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.zeapo.pwdstore.R
|
import com.zeapo.pwdstore.R
|
||||||
@@ -20,6 +21,7 @@ import com.zeapo.pwdstore.utils.PasswordRepository
|
|||||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||||
import com.zeapo.pwdstore.utils.viewBinding
|
import com.zeapo.pwdstore.utils.viewBinding
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity that encompasses both the initial clone as well as editing the server config for future
|
* Activity that encompasses both the initial clone as well as editing the server config for future
|
||||||
@@ -171,7 +173,7 @@ class GitServerConfigActivity : BaseGitActivity() {
|
|||||||
.setPositiveButton(R.string.dialog_delete) { dialog, _ ->
|
.setPositiveButton(R.string.dialog_delete) { dialog, _ ->
|
||||||
try {
|
try {
|
||||||
localDir.deleteRecursively()
|
localDir.deleteRecursively()
|
||||||
launchGitOperation(REQUEST_CLONE)
|
lifecycleScope.launch { launchGitOperation(REQUEST_CLONE) }
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
// TODO Handle the exception correctly if we are unable to delete the directory...
|
// TODO Handle the exception correctly if we are unable to delete the directory...
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@@ -201,7 +203,7 @@ class GitServerConfigActivity : BaseGitActivity() {
|
|||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
MaterialAlertDialogBuilder(this).setMessage(e.message).show()
|
MaterialAlertDialogBuilder(this).setMessage(e.message).show()
|
||||||
}
|
}
|
||||||
launchGitOperation(REQUEST_CLONE)
|
lifecycleScope.launch { launchGitOperation(REQUEST_CLONE) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
package com.zeapo.pwdstore.git
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.zeapo.pwdstore.R
|
|
||||||
import java.io.File
|
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import org.eclipse.jgit.api.PullCommand
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new git operation
|
|
||||||
*
|
|
||||||
* @param fileDir the git working tree directory
|
|
||||||
* @param callingActivity the calling activity
|
|
||||||
*/
|
|
||||||
class PullOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the command
|
|
||||||
*
|
|
||||||
* @return the current object
|
|
||||||
*/
|
|
||||||
fun setCommand(): PullOperation {
|
|
||||||
this.command = Git(repository)
|
|
||||||
.pull()
|
|
||||||
.setRebase(true)
|
|
||||||
.setRemote("origin")
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun execute() {
|
|
||||||
(this.command as? PullCommand)?.setCredentialsProvider(this.provider)
|
|
||||||
GitAsyncTask(callingActivity, this, Intent()).execute(this.command)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(err: Exception) {
|
|
||||||
super.onError(err)
|
|
||||||
MaterialAlertDialogBuilder(callingActivity)
|
|
||||||
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
|
|
||||||
.setMessage("Error occurred during the pull operation, " +
|
|
||||||
callingActivity.resources.getString(R.string.jgit_error_dialog_text) +
|
|
||||||
err.message +
|
|
||||||
"\nPlease check the FAQ for possible reasons why this error might occur.")
|
|
||||||
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> callingActivity.finish() }
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
package com.zeapo.pwdstore.git
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.zeapo.pwdstore.R
|
|
||||||
import java.io.File
|
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import org.eclipse.jgit.api.PushCommand
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new git operation
|
|
||||||
*
|
|
||||||
* @param fileDir the git working tree directory
|
|
||||||
* @param callingActivity the calling activity
|
|
||||||
*/
|
|
||||||
class PushOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the command
|
|
||||||
*
|
|
||||||
* @return the current object
|
|
||||||
*/
|
|
||||||
fun setCommand(): PushOperation {
|
|
||||||
this.command = Git(repository)
|
|
||||||
.push()
|
|
||||||
.setPushAll()
|
|
||||||
.setRemote("origin")
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun execute() {
|
|
||||||
(this.command as? PushCommand)?.setCredentialsProvider(this.provider)
|
|
||||||
GitAsyncTask(callingActivity, this, Intent()).execute(this.command)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(err: Exception) {
|
|
||||||
// TODO handle the "Nothing to push" case
|
|
||||||
super.onError(err)
|
|
||||||
MaterialAlertDialogBuilder(callingActivity)
|
|
||||||
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
|
|
||||||
.setMessage(callingActivity.getString(R.string.jgit_error_push_dialog_text) + err.message)
|
|
||||||
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> callingActivity.finish() }
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
package com.zeapo.pwdstore.git
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.zeapo.pwdstore.R
|
|
||||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
|
||||||
import java.io.File
|
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import org.eclipse.jgit.api.GitCommand
|
|
||||||
import org.eclipse.jgit.api.ResetCommand
|
|
||||||
import org.eclipse.jgit.api.TransportCommand
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new git operation
|
|
||||||
*
|
|
||||||
* @param fileDir the git working tree directory
|
|
||||||
* @param callingActivity the calling activity
|
|
||||||
*/
|
|
||||||
class ResetToRemoteOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
|
|
||||||
|
|
||||||
private lateinit var commands: List<GitCommand<out Any>>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the command
|
|
||||||
*
|
|
||||||
* @return the current object
|
|
||||||
*/
|
|
||||||
fun setCommands(): ResetToRemoteOperation {
|
|
||||||
val remoteBranch = PreferenceManager
|
|
||||||
.getDefaultSharedPreferences(callingActivity.applicationContext)
|
|
||||||
.getString(PreferenceKeys.GIT_BRANCH_NAME, "master")
|
|
||||||
val git = Git(repository)
|
|
||||||
val cmds = arrayListOf(
|
|
||||||
git.add().addFilepattern("."),
|
|
||||||
git.fetch().setRemote("origin"),
|
|
||||||
git.reset().setRef("origin/$remoteBranch").setMode(ResetCommand.ResetType.HARD)
|
|
||||||
)
|
|
||||||
if (git.branchList().call().none { it.name == remoteBranch }) {
|
|
||||||
cmds.add(
|
|
||||||
git.branchCreate().setName(remoteBranch).setForce(true)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
commands = cmds
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun execute() {
|
|
||||||
commands.filterIsInstance<TransportCommand<*, *>>().map { it.setCredentialsProvider(provider) }
|
|
||||||
GitAsyncTask(callingActivity, this, Intent()).execute(*commands.toTypedArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(err: Exception) {
|
|
||||||
super.onError(err)
|
|
||||||
MaterialAlertDialogBuilder(callingActivity)
|
|
||||||
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
|
|
||||||
.setMessage("Error occurred 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) +
|
|
||||||
err)
|
|
||||||
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> }
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
package com.zeapo.pwdstore.git
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.zeapo.pwdstore.R
|
|
||||||
import java.io.File
|
|
||||||
import org.eclipse.jgit.api.AddCommand
|
|
||||||
import org.eclipse.jgit.api.CommitCommand
|
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import org.eclipse.jgit.api.PullCommand
|
|
||||||
import org.eclipse.jgit.api.PushCommand
|
|
||||||
import org.eclipse.jgit.api.StatusCommand
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new git operation
|
|
||||||
*
|
|
||||||
* @param fileDir the git working tree directory
|
|
||||||
* @param callingActivity the calling activity
|
|
||||||
*/
|
|
||||||
class SyncOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
|
|
||||||
|
|
||||||
private var addCommand: AddCommand? = null
|
|
||||||
private var statusCommand: StatusCommand? = null
|
|
||||||
private var commitCommand: CommitCommand? = null
|
|
||||||
private var pullCommand: PullCommand? = null
|
|
||||||
private var pushCommand: PushCommand? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the command
|
|
||||||
*
|
|
||||||
* @return the current object
|
|
||||||
*/
|
|
||||||
fun setCommands(): SyncOperation {
|
|
||||||
val git = Git(repository)
|
|
||||||
this.addCommand = git.add().addFilepattern(".")
|
|
||||||
this.statusCommand = git.status()
|
|
||||||
this.commitCommand = git.commit().setAll(true).setMessage("[Android Password Store] Sync")
|
|
||||||
this.pullCommand = git.pull().setRebase(true).setRemote("origin")
|
|
||||||
this.pushCommand = git.push().setPushAll().setRemote("origin")
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun execute() {
|
|
||||||
if (this.provider != null) {
|
|
||||||
this.pullCommand?.setCredentialsProvider(this.provider)
|
|
||||||
this.pushCommand?.setCredentialsProvider(this.provider)
|
|
||||||
}
|
|
||||||
GitAsyncTask(callingActivity, this, Intent()).execute(this.addCommand, this.statusCommand, this.commitCommand, this.pullCommand, this.pushCommand)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(err: Exception) {
|
|
||||||
super.onError(err)
|
|
||||||
MaterialAlertDialogBuilder(callingActivity)
|
|
||||||
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
|
|
||||||
.setMessage("Error occurred 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) +
|
|
||||||
err)
|
|
||||||
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> callingActivity.finish() }
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
package com.zeapo.pwdstore.git.operation
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.zeapo.pwdstore.R
|
||||||
|
import com.zeapo.pwdstore.git.GitCommandExecutor
|
||||||
|
import java.io.File
|
||||||
|
import org.eclipse.jgit.api.RebaseCommand
|
||||||
|
|
||||||
|
class BreakOutOfDetached(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
|
||||||
|
|
||||||
|
private val branchName = "conflicting-$remoteBranch-${System.currentTimeMillis()}"
|
||||||
|
|
||||||
|
override val commands = arrayOf(
|
||||||
|
// abort the rebase
|
||||||
|
git.rebase().setOperation(RebaseCommand.Operation.ABORT),
|
||||||
|
// git checkout -b conflict-branch
|
||||||
|
git.checkout().setCreateBranch(true).setName(branchName),
|
||||||
|
// push the changes
|
||||||
|
git.push().setRemote("origin"),
|
||||||
|
// switch back to ${gitBranch}
|
||||||
|
git.checkout().setName(remoteBranch),
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun execute() {
|
||||||
|
if (!git.repository.repositoryState.isRebasing) {
|
||||||
|
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)) { _, _ ->
|
||||||
|
callingActivity.finish()
|
||||||
|
}.show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
GitCommandExecutor(callingActivity, this).execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess() {
|
||||||
|
MaterialAlertDialogBuilder(callingActivity)
|
||||||
|
.setTitle(callingActivity.resources.getString(R.string.git_abort_and_push_title))
|
||||||
|
.setMessage("There was a conflict when trying to rebase. " +
|
||||||
|
"Your local $remoteBranch branch was pushed to another branch named conflicting-$remoteBranch-....\n" +
|
||||||
|
"Use this branch to resolve conflict on your computer")
|
||||||
|
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
|
||||||
|
callingActivity.finish()
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
package com.zeapo.pwdstore.git.operation
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.zeapo.pwdstore.git.GitCommandExecutor
|
||||||
|
import java.io.File
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.api.GitCommand
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new clone operation
|
||||||
|
*
|
||||||
|
* @param fileDir the git working tree directory
|
||||||
|
* @param uri URL to clone the repository from
|
||||||
|
* @param callingActivity the calling activity
|
||||||
|
*/
|
||||||
|
class CloneOperation(fileDir: File, uri: String, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
|
||||||
|
|
||||||
|
override val commands: Array<GitCommand<out Any>> = arrayOf(
|
||||||
|
Git.cloneRepository().setBranch(remoteBranch).setDirectory(repository?.workTree).setURI(uri),
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun execute() {
|
||||||
|
GitCommandExecutor(callingActivity, this).execute()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,94 @@
|
|||||||
|
package com.zeapo.pwdstore.git.operation
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import com.google.android.material.checkbox.MaterialCheckBox
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import com.zeapo.pwdstore.R
|
||||||
|
import com.zeapo.pwdstore.git.config.ConnectionMode
|
||||||
|
import com.zeapo.pwdstore.git.config.InteractivePasswordFinder
|
||||||
|
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||||
|
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
||||||
|
import com.zeapo.pwdstore.utils.requestInputFocusOnView
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
|
class CredentialFinder(
|
||||||
|
val callingActivity: FragmentActivity,
|
||||||
|
val connectionMode: ConnectionMode
|
||||||
|
) : InteractivePasswordFinder() {
|
||||||
|
|
||||||
|
override fun askForPassword(cont: Continuation<String?>, isRetry: Boolean) {
|
||||||
|
val gitOperationPrefs = callingActivity.getEncryptedPrefs("git_operation")
|
||||||
|
val credentialPref: String
|
||||||
|
@StringRes val messageRes: Int
|
||||||
|
@StringRes val hintRes: Int
|
||||||
|
@StringRes val rememberRes: Int
|
||||||
|
@StringRes val errorRes: Int
|
||||||
|
when (connectionMode) {
|
||||||
|
ConnectionMode.SshKey -> {
|
||||||
|
credentialPref = PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE
|
||||||
|
messageRes = R.string.passphrase_dialog_text
|
||||||
|
hintRes = R.string.ssh_keygen_passphrase
|
||||||
|
rememberRes = R.string.git_operation_remember_passphrase
|
||||||
|
errorRes = R.string.git_operation_wrong_passphrase
|
||||||
|
}
|
||||||
|
ConnectionMode.Password -> {
|
||||||
|
// Could be either an SSH or an HTTPS password
|
||||||
|
credentialPref = PreferenceKeys.HTTPS_PASSWORD
|
||||||
|
messageRes = R.string.password_dialog_text
|
||||||
|
hintRes = R.string.git_operation_hint_password
|
||||||
|
rememberRes = R.string.git_operation_remember_password
|
||||||
|
errorRes = R.string.git_operation_wrong_password
|
||||||
|
}
|
||||||
|
else -> throw IllegalStateException("Only SshKey and Password connection mode ask for passwords")
|
||||||
|
}
|
||||||
|
val storedCredential = gitOperationPrefs.getString(credentialPref, null)
|
||||||
|
if (isRetry)
|
||||||
|
gitOperationPrefs.edit { remove(credentialPref) }
|
||||||
|
if (storedCredential == null) {
|
||||||
|
val layoutInflater = LayoutInflater.from(callingActivity)
|
||||||
|
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
|
val dialogView = layoutInflater.inflate(R.layout.git_credential_layout, null)
|
||||||
|
val credentialLayout = dialogView.findViewById<TextInputLayout>(R.id.git_auth_passphrase_layout)
|
||||||
|
val editCredential = dialogView.findViewById<TextInputEditText>(R.id.git_auth_credential)
|
||||||
|
editCredential.setHint(hintRes)
|
||||||
|
val rememberCredential = dialogView.findViewById<MaterialCheckBox>(R.id.git_auth_remember_credential)
|
||||||
|
rememberCredential.setText(rememberRes)
|
||||||
|
if (isRetry)
|
||||||
|
credentialLayout.error = callingActivity.resources.getString(errorRes)
|
||||||
|
MaterialAlertDialogBuilder(callingActivity).run {
|
||||||
|
setTitle(R.string.passphrase_dialog_title)
|
||||||
|
setMessage(messageRes)
|
||||||
|
setView(dialogView)
|
||||||
|
setPositiveButton(R.string.dialog_ok) { _, _ ->
|
||||||
|
val credential = editCredential.text.toString()
|
||||||
|
if (rememberCredential.isChecked) {
|
||||||
|
gitOperationPrefs.edit {
|
||||||
|
putString(credentialPref, credential)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cont.resume(credential)
|
||||||
|
}
|
||||||
|
setNegativeButton(R.string.dialog_cancel) { _, _ ->
|
||||||
|
cont.resume(null)
|
||||||
|
}
|
||||||
|
setOnCancelListener {
|
||||||
|
cont.resume(null)
|
||||||
|
}
|
||||||
|
create()
|
||||||
|
}.run {
|
||||||
|
requestInputFocusOnView<TextInputEditText>(R.id.git_auth_credential)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cont.resume(storedCredential)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -2,21 +2,18 @@
|
|||||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
*/
|
*/
|
||||||
package com.zeapo.pwdstore.git
|
package com.zeapo.pwdstore.git.operation
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.view.LayoutInflater
|
import androidx.annotation.CallSuper
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.checkbox.MaterialCheckBox
|
import com.github.ajalt.timberkt.Timber.d
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.textfield.TextInputEditText
|
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
|
||||||
import com.zeapo.pwdstore.R
|
import com.zeapo.pwdstore.R
|
||||||
import com.zeapo.pwdstore.UserPreference
|
import com.zeapo.pwdstore.UserPreference
|
||||||
|
import com.zeapo.pwdstore.git.ErrorMessages
|
||||||
import com.zeapo.pwdstore.git.config.ConnectionMode
|
import com.zeapo.pwdstore.git.config.ConnectionMode
|
||||||
import com.zeapo.pwdstore.git.config.InteractivePasswordFinder
|
import com.zeapo.pwdstore.git.config.InteractivePasswordFinder
|
||||||
import com.zeapo.pwdstore.git.config.SshApiSessionFactory
|
import com.zeapo.pwdstore.git.config.SshApiSessionFactory
|
||||||
@@ -25,108 +22,34 @@ import com.zeapo.pwdstore.git.config.SshjSessionFactory
|
|||||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||||
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
||||||
import com.zeapo.pwdstore.utils.requestInputFocusOnView
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.coroutines.Continuation
|
|
||||||
import kotlin.coroutines.resume
|
|
||||||
import net.schmizz.sshj.userauth.password.PasswordFinder
|
import net.schmizz.sshj.userauth.password.PasswordFinder
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.api.GitCommand
|
import org.eclipse.jgit.api.GitCommand
|
||||||
|
import org.eclipse.jgit.api.TransportCommand
|
||||||
import org.eclipse.jgit.errors.UnsupportedCredentialItem
|
import org.eclipse.jgit.errors.UnsupportedCredentialItem
|
||||||
import org.eclipse.jgit.lib.Repository
|
|
||||||
import org.eclipse.jgit.transport.CredentialItem
|
import org.eclipse.jgit.transport.CredentialItem
|
||||||
import org.eclipse.jgit.transport.CredentialsProvider
|
import org.eclipse.jgit.transport.CredentialsProvider
|
||||||
import org.eclipse.jgit.transport.SshSessionFactory
|
import org.eclipse.jgit.transport.SshSessionFactory
|
||||||
import org.eclipse.jgit.transport.URIish
|
import org.eclipse.jgit.transport.URIish
|
||||||
|
|
||||||
|
|
||||||
private class GitOperationCredentialFinder(
|
|
||||||
val callingActivity: AppCompatActivity,
|
|
||||||
val connectionMode: ConnectionMode
|
|
||||||
) : InteractivePasswordFinder() {
|
|
||||||
|
|
||||||
override fun askForPassword(cont: Continuation<String?>, isRetry: Boolean) {
|
|
||||||
val gitOperationPrefs = callingActivity.getEncryptedPrefs("git_operation")
|
|
||||||
val credentialPref: String
|
|
||||||
@StringRes val messageRes: Int
|
|
||||||
@StringRes val hintRes: Int
|
|
||||||
@StringRes val rememberRes: Int
|
|
||||||
@StringRes val errorRes: Int
|
|
||||||
when (connectionMode) {
|
|
||||||
ConnectionMode.SshKey -> {
|
|
||||||
credentialPref = PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE
|
|
||||||
messageRes = R.string.passphrase_dialog_text
|
|
||||||
hintRes = R.string.ssh_keygen_passphrase
|
|
||||||
rememberRes = R.string.git_operation_remember_passphrase
|
|
||||||
errorRes = R.string.git_operation_wrong_passphrase
|
|
||||||
}
|
|
||||||
ConnectionMode.Password -> {
|
|
||||||
// Could be either an SSH or an HTTPS password
|
|
||||||
credentialPref = PreferenceKeys.HTTPS_PASSWORD
|
|
||||||
messageRes = R.string.password_dialog_text
|
|
||||||
hintRes = R.string.git_operation_hint_password
|
|
||||||
rememberRes = R.string.git_operation_remember_password
|
|
||||||
errorRes = R.string.git_operation_wrong_password
|
|
||||||
}
|
|
||||||
else -> throw IllegalStateException("Only SshKey and Password connection mode ask for passwords")
|
|
||||||
}
|
|
||||||
val storedCredential = gitOperationPrefs.getString(credentialPref, null)
|
|
||||||
if (isRetry)
|
|
||||||
gitOperationPrefs.edit { remove(credentialPref) }
|
|
||||||
if (storedCredential == null) {
|
|
||||||
val layoutInflater = LayoutInflater.from(callingActivity)
|
|
||||||
|
|
||||||
@SuppressLint("InflateParams")
|
|
||||||
val dialogView = layoutInflater.inflate(R.layout.git_credential_layout, null)
|
|
||||||
val credentialLayout = dialogView.findViewById<TextInputLayout>(R.id.git_auth_passphrase_layout)
|
|
||||||
val editCredential = dialogView.findViewById<TextInputEditText>(R.id.git_auth_credential)
|
|
||||||
editCredential.setHint(hintRes)
|
|
||||||
val rememberCredential = dialogView.findViewById<MaterialCheckBox>(R.id.git_auth_remember_credential)
|
|
||||||
rememberCredential.setText(rememberRes)
|
|
||||||
if (isRetry)
|
|
||||||
credentialLayout.error = callingActivity.resources.getString(errorRes)
|
|
||||||
MaterialAlertDialogBuilder(callingActivity).run {
|
|
||||||
setTitle(R.string.passphrase_dialog_title)
|
|
||||||
setMessage(messageRes)
|
|
||||||
setView(dialogView)
|
|
||||||
setPositiveButton(R.string.dialog_ok) { _, _ ->
|
|
||||||
val credential = editCredential.text.toString()
|
|
||||||
if (rememberCredential.isChecked) {
|
|
||||||
gitOperationPrefs.edit {
|
|
||||||
putString(credentialPref, credential)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cont.resume(credential)
|
|
||||||
}
|
|
||||||
setNegativeButton(R.string.dialog_cancel) { _, _ ->
|
|
||||||
cont.resume(null)
|
|
||||||
}
|
|
||||||
setOnCancelListener {
|
|
||||||
cont.resume(null)
|
|
||||||
}
|
|
||||||
create()
|
|
||||||
}.run {
|
|
||||||
requestInputFocusOnView<TextInputEditText>(R.id.git_auth_credential)
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cont.resume(storedCredential)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new git operation
|
* Creates a new git operation
|
||||||
*
|
*
|
||||||
* @param gitDir the git working tree directory
|
* @param gitDir the git working tree directory
|
||||||
* @param callingActivity the calling activity
|
* @param callingActivity the calling activity
|
||||||
*/
|
*/
|
||||||
abstract class GitOperation(gitDir: File, internal val callingActivity: AppCompatActivity) {
|
abstract class GitOperation(gitDir: File, internal val callingActivity: FragmentActivity) {
|
||||||
|
|
||||||
protected val repository: Repository? = PasswordRepository.getRepository(gitDir)
|
abstract val commands: Array<GitCommand<out Any>>
|
||||||
internal var provider: CredentialsProvider? = null
|
private var provider: CredentialsProvider? = null
|
||||||
internal var command: GitCommand<*>? = null
|
|
||||||
private val sshKeyFile = callingActivity.filesDir.resolve(".ssh_key")
|
private val sshKeyFile = callingActivity.filesDir.resolve(".ssh_key")
|
||||||
private val hostKeyFile = callingActivity.filesDir.resolve(".host_key")
|
private val hostKeyFile = callingActivity.filesDir.resolve(".host_key")
|
||||||
|
protected val repository = PasswordRepository.getRepository(gitDir)
|
||||||
|
protected val git = Git(repository)
|
||||||
|
protected val remoteBranch = PreferenceManager
|
||||||
|
.getDefaultSharedPreferences(callingActivity.applicationContext)
|
||||||
|
.getString(PreferenceKeys.GIT_BRANCH_NAME, "master")
|
||||||
|
|
||||||
private class PasswordFinderCredentialsProvider(private val username: String, private val passwordFinder: PasswordFinder) : CredentialsProvider() {
|
private class PasswordFinderCredentialsProvider(private val username: String, private val passwordFinder: PasswordFinder) : CredentialsProvider() {
|
||||||
|
|
||||||
@@ -181,12 +104,18 @@ abstract class GitOperation(gitDir: File, internal val callingActivity: AppCompa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setCredentialProvider() {
|
||||||
|
provider?.let { credentialsProvider ->
|
||||||
|
commands.filterIsInstance<TransportCommand<*, *>>().forEach { it.setCredentialsProvider(credentialsProvider) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the GitCommand in an async task
|
* Executes the GitCommand in an async task
|
||||||
*/
|
*/
|
||||||
abstract fun execute()
|
abstract suspend fun execute()
|
||||||
|
|
||||||
fun executeAfterAuthentication(
|
suspend fun executeAfterAuthentication(
|
||||||
connectionMode: ConnectionMode,
|
connectionMode: ConnectionMode,
|
||||||
username: String,
|
username: String,
|
||||||
identity: SshApiSessionFactory.ApiIdentity?
|
identity: SshApiSessionFactory.ApiIdentity?
|
||||||
@@ -207,12 +136,12 @@ abstract class GitOperation(gitDir: File, internal val callingActivity: AppCompa
|
|||||||
callingActivity.finish()
|
callingActivity.finish()
|
||||||
}.show()
|
}.show()
|
||||||
} else {
|
} else {
|
||||||
withPublicKeyAuthentication(username, GitOperationCredentialFinder(callingActivity,
|
withPublicKeyAuthentication(username, CredentialFinder(callingActivity,
|
||||||
connectionMode)).execute()
|
connectionMode)).execute()
|
||||||
}
|
}
|
||||||
ConnectionMode.OpenKeychain -> withOpenKeychainAuthentication(username, identity).execute()
|
ConnectionMode.OpenKeychain -> withOpenKeychainAuthentication(username, identity).execute()
|
||||||
ConnectionMode.Password -> withPasswordAuthentication(
|
ConnectionMode.Password -> withPasswordAuthentication(
|
||||||
username, GitOperationCredentialFinder(callingActivity, connectionMode)).execute()
|
username, CredentialFinder(callingActivity, connectionMode)).execute()
|
||||||
ConnectionMode.None -> execute()
|
ConnectionMode.None -> execute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,6 +149,7 @@ abstract class GitOperation(gitDir: File, internal val callingActivity: AppCompa
|
|||||||
/**
|
/**
|
||||||
* Action to execute on error
|
* Action to execute on error
|
||||||
*/
|
*/
|
||||||
|
@CallSuper
|
||||||
open fun onError(err: Exception) {
|
open fun onError(err: Exception) {
|
||||||
// Clear various auth related fields on failure
|
// Clear various auth related fields on failure
|
||||||
when (SshSessionFactory.getInstance()) {
|
when (SshSessionFactory.getInstance()) {
|
||||||
@@ -236,6 +166,13 @@ abstract class GitOperation(gitDir: File, internal val callingActivity: AppCompa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
d(err)
|
||||||
|
MaterialAlertDialogBuilder(callingActivity)
|
||||||
|
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
|
||||||
|
.setMessage(ErrorMessages[err])
|
||||||
|
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
|
||||||
|
callingActivity.finish()
|
||||||
|
}.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
package com.zeapo.pwdstore.git.operation
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.zeapo.pwdstore.git.GitCommandExecutor
|
||||||
|
import java.io.File
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.api.GitCommand
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new git operation
|
||||||
|
*
|
||||||
|
* @param fileDir the git working tree directory
|
||||||
|
* @param callingActivity the calling activity
|
||||||
|
*/
|
||||||
|
class PullOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
|
||||||
|
|
||||||
|
override val commands: Array<GitCommand<out Any>> = arrayOf(
|
||||||
|
Git(repository).pull().setRebase(true).setRemote("origin"),
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun execute() {
|
||||||
|
GitCommandExecutor(callingActivity, this).execute()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
package com.zeapo.pwdstore.git.operation
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.zeapo.pwdstore.git.GitCommandExecutor
|
||||||
|
import java.io.File
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.api.GitCommand
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new git operation
|
||||||
|
*
|
||||||
|
* @param fileDir the git working tree directory
|
||||||
|
* @param callingActivity the calling activity
|
||||||
|
*/
|
||||||
|
class PushOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
|
||||||
|
|
||||||
|
override val commands: Array<GitCommand<out Any>> = arrayOf(
|
||||||
|
Git(repository).push().setPushAll().setRemote("origin"),
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun execute() {
|
||||||
|
setCredentialProvider()
|
||||||
|
GitCommandExecutor(callingActivity, this).execute()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
package com.zeapo.pwdstore.git.operation
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.zeapo.pwdstore.git.GitCommandExecutor
|
||||||
|
import java.io.File
|
||||||
|
import org.eclipse.jgit.api.ResetCommand
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new git operation
|
||||||
|
*
|
||||||
|
* @param fileDir the git working tree directory
|
||||||
|
* @param callingActivity the calling activity
|
||||||
|
*/
|
||||||
|
class ResetToRemoteOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
|
||||||
|
|
||||||
|
override val commands = arrayOf(
|
||||||
|
git.add().addFilepattern("."),
|
||||||
|
git.fetch().setRemote("origin"),
|
||||||
|
git.reset().setRef("origin/$remoteBranch").setMode(ResetCommand.ResetType.HARD),
|
||||||
|
git.branchCreate().setName(remoteBranch).setForce(true),
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun execute() {
|
||||||
|
GitCommandExecutor(callingActivity, this).execute()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
package com.zeapo.pwdstore.git.operation
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.zeapo.pwdstore.git.GitCommandExecutor
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new git operation
|
||||||
|
*
|
||||||
|
* @param fileDir the git working tree directory
|
||||||
|
* @param callingActivity the calling activity
|
||||||
|
*/
|
||||||
|
class SyncOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
|
||||||
|
|
||||||
|
override val commands = arrayOf(
|
||||||
|
git.add().addFilepattern("."),
|
||||||
|
git.status(),
|
||||||
|
git.commit().setAll(true).setMessage("[Android Password Store] Sync"),
|
||||||
|
git.pull().setRebase(true).setRemote("origin"),
|
||||||
|
git.push().setPushAll().setRemote("origin"),
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun execute() {
|
||||||
|
GitCommandExecutor(callingActivity, this).execute()
|
||||||
|
}
|
||||||
|
}
|
@@ -14,20 +14,18 @@ import android.view.View
|
|||||||
import android.view.autofill.AutofillManager
|
import android.view.autofill.AutofillManager
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import androidx.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
import androidx.annotation.MainThread
|
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.security.crypto.EncryptedSharedPreferences
|
import androidx.security.crypto.EncryptedSharedPreferences
|
||||||
import androidx.security.crypto.MasterKey
|
import androidx.security.crypto.MasterKey
|
||||||
import com.github.ajalt.timberkt.d
|
import com.github.ajalt.timberkt.d
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.zeapo.pwdstore.git.GitAsyncTask
|
import com.zeapo.pwdstore.git.GitCommandExecutor
|
||||||
import com.zeapo.pwdstore.git.GitOperation
|
import com.zeapo.pwdstore.git.operation.GitOperation
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository.Companion.getRepositoryDirectory
|
import com.zeapo.pwdstore.utils.PasswordRepository.Companion.getRepositoryDirectory
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
|
|
||||||
const val OPENPGP_PROVIDER = "org.sufficientlysecure.keychain"
|
const val OPENPGP_PROVIDER = "org.sufficientlysecure.keychain"
|
||||||
|
|
||||||
@@ -51,12 +49,14 @@ fun CharArray.clear() {
|
|||||||
|
|
||||||
val Context.clipboard get() = getSystemService<ClipboardManager>()
|
val Context.clipboard get() = getSystemService<ClipboardManager>()
|
||||||
|
|
||||||
fun AppCompatActivity.snackbar(
|
fun FragmentActivity.snackbar(
|
||||||
view: View = findViewById(android.R.id.content),
|
view: View = findViewById(android.R.id.content),
|
||||||
message: String,
|
message: String,
|
||||||
length: Int = Snackbar.LENGTH_SHORT
|
length: Int = Snackbar.LENGTH_SHORT,
|
||||||
) {
|
): Snackbar {
|
||||||
Snackbar.make(view, message, length).show()
|
val snackbar = Snackbar.make(view, message, length)
|
||||||
|
snackbar.show()
|
||||||
|
return snackbar
|
||||||
}
|
}
|
||||||
|
|
||||||
fun File.listFilesRecursively() = walkTopDown().filter { !it.isDirectory }.toList()
|
fun File.listFilesRecursively() = walkTopDown().filter { !it.isDirectory }.toList()
|
||||||
@@ -97,24 +97,33 @@ fun Context.getEncryptedPrefs(fileName: String): SharedPreferences {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
suspend fun FragmentActivity.commitChange(
|
||||||
fun AppCompatActivity.commitChange(message: String, finishWithResultOnEnd: Intent? = null) {
|
message: String,
|
||||||
|
finishWithResultOnEnd: Intent? = null,
|
||||||
|
finishActivityOnEnd: Boolean = true,
|
||||||
|
) {
|
||||||
if (!PasswordRepository.isGitRepo()) {
|
if (!PasswordRepository.isGitRepo()) {
|
||||||
if (finishWithResultOnEnd != null) {
|
if (finishWithResultOnEnd != null) {
|
||||||
setResult(AppCompatActivity.RESULT_OK, finishWithResultOnEnd)
|
setResult(FragmentActivity.RESULT_OK, finishWithResultOnEnd)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
object : GitOperation(getRepositoryDirectory(this@commitChange), this@commitChange) {
|
object : GitOperation(getRepositoryDirectory(this@commitChange), this@commitChange) {
|
||||||
override fun execute() {
|
override val commands = arrayOf(
|
||||||
|
git.add().addFilepattern("."),
|
||||||
|
git.status(),
|
||||||
|
git.commit().setAll(true).setMessage(message),
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun execute() {
|
||||||
d { "Comitting with message: '$message'" }
|
d { "Comitting with message: '$message'" }
|
||||||
val git = Git(repository)
|
GitCommandExecutor(
|
||||||
val task = GitAsyncTask(this@commitChange, this, finishWithResultOnEnd, silentlyExecute = true)
|
this@commitChange,
|
||||||
task.execute(
|
this,
|
||||||
git.add().addFilepattern("."),
|
finishWithResultOnEnd,
|
||||||
git.commit().setAll(true).setMessage(message)
|
finishActivityOnEnd,
|
||||||
)
|
).execute()
|
||||||
}
|
}
|
||||||
}.execute()
|
}.execute()
|
||||||
}
|
}
|
||||||
@@ -124,7 +133,6 @@ fun AppCompatActivity.commitChange(message: String, finishWithResultOnEnd: Inten
|
|||||||
* view whose id is [id]. Solution based on a StackOverflow
|
* view whose id is [id]. Solution based on a StackOverflow
|
||||||
* answer: https://stackoverflow.com/a/13056259/297261
|
* answer: https://stackoverflow.com/a/13056259/297261
|
||||||
*/
|
*/
|
||||||
@MainThread
|
|
||||||
fun <T : View> AlertDialog.requestInputFocusOnView(@IdRes id: Int) {
|
fun <T : View> AlertDialog.requestInputFocusOnView(@IdRes id: Int) {
|
||||||
setOnShowListener {
|
setOnShowListener {
|
||||||
findViewById<T>(id)?.apply {
|
findViewById<T>(id)?.apply {
|
||||||
@@ -143,6 +151,6 @@ val Context.autofillManager: AutofillManager?
|
|||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
get() = getSystemService()
|
get() = getSystemService()
|
||||||
|
|
||||||
fun AppCompatActivity.isInsideRepository(file: File): Boolean {
|
fun FragmentActivity.isInsideRepository(file: File): Boolean {
|
||||||
return file.canonicalPath.contains(getRepositoryDirectory(this).canonicalPath)
|
return file.canonicalPath.contains(getRepositoryDirectory(this).canonicalPath)
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,6 @@ import androidx.fragment.app.Fragment
|
|||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.observe
|
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import kotlin.properties.ReadOnlyProperty
|
import kotlin.properties.ReadOnlyProperty
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
16
app/src/main/java/com/zeapo/pwdstore/utils/Result.kt
Normal file
16
app/src/main/java/com/zeapo/pwdstore/utils/Result.kt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.zeapo.pwdstore.utils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates the Rust Result enum but without returning a value in the [Ok] case.
|
||||||
|
* https://doc.rust-lang.org/std/result/enum.Result.html
|
||||||
|
*/
|
||||||
|
sealed class Result {
|
||||||
|
|
||||||
|
object Ok : Result()
|
||||||
|
data class Err(val err: Exception) : Result()
|
||||||
|
}
|
@@ -57,6 +57,7 @@ class UriTotpFinder : TotpFinder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
val TOTP_FIELDS = arrayOf(
|
val TOTP_FIELDS = arrayOf(
|
||||||
"otpauth://totp",
|
"otpauth://totp",
|
||||||
"totp:"
|
"totp:"
|
||||||
|
@@ -276,10 +276,6 @@
|
|||||||
<string name="autofill_ins_1_hint">Screenshot of accessibility services</string>
|
<string name="autofill_ins_1_hint">Screenshot of accessibility services</string>
|
||||||
<string name="autofill_ins_2_hint">Screenshot of toggle in accessibility services</string>
|
<string name="autofill_ins_2_hint">Screenshot of toggle in accessibility services</string>
|
||||||
<string name="autofill_ins_3_hint">Screenshot of autofill service in action</string>
|
<string name="autofill_ins_3_hint">Screenshot of autofill service in action</string>
|
||||||
<string name="git_pull_fail_error">Pull has failed, you\'re in a detached head. Using "settings > git utils", save your changes to the remote in a new branch and resolve the conflict on your computer.</string>
|
|
||||||
<string name="git_push_nff_error">Push was rejected by remote, run pull before pushing again. You can use Synchronize rather than pull/push as it implements both</string>
|
|
||||||
<string name="git_push_generic_error">Push was rejected by remote, reason:</string>
|
|
||||||
<string name="git_push_other_error">Remote rejected non-fast-forward push. Check receive.denyNonFastForwards variable in config file of destination repository.</string>
|
|
||||||
<string name="jgit_error_push_dialog_text">Error occurred during the push operation:</string>
|
<string name="jgit_error_push_dialog_text">Error occurred during the push operation:</string>
|
||||||
<string name="clear_saved_passphrase_ssh">Clear saved passphrase for local SSH key</string>
|
<string name="clear_saved_passphrase_ssh">Clear saved passphrase for local SSH key</string>
|
||||||
<string name="clear_saved_passphrase_https">Clear saved HTTPS password</string>
|
<string name="clear_saved_passphrase_https">Clear saved HTTPS password</string>
|
||||||
@@ -371,4 +367,12 @@
|
|||||||
<string name="short_key_ids_unsupported">A key ID in .gpg-id is too short, please use either long key IDs (16 characters) or fingerprints (40 characters)</string>
|
<string name="short_key_ids_unsupported">A key ID in .gpg-id is too short, please use either long key IDs (16 characters) or fingerprints (40 characters)</string>
|
||||||
<string name="invalid_filename_text">File name must not contain \'/\', set directory above</string>
|
<string name="invalid_filename_text">File name must not contain \'/\', set directory above</string>
|
||||||
<string name="directory_hint">Directory</string>
|
<string name="directory_hint">Directory</string>
|
||||||
|
|
||||||
|
<!-- GitException messages -->
|
||||||
|
<string name="git_unknown_error">Unknown error</string>
|
||||||
|
<string name="git_pull_fail_error">Pull has failed, you\'re in a detached head. Using "settings > git utils", save your changes to the remote in a new branch and resolve the conflict on your computer.</string>
|
||||||
|
<string name="git_push_nff_error">Push was rejected by remote, run pull before pushing again. You can use Synchronize rather than pull/push as it implements both</string>
|
||||||
|
<string name="git_push_generic_error">Push was rejected by remote, reason: %1$s</string>
|
||||||
|
<string name="git_push_other_error">Remote rejected non-fast-forward push. Check receive.denyNonFastForwards variable in config file of destination repository.</string>
|
||||||
|
<string name="git_operation_running">Running git operation…</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Reference in New Issue
Block a user