aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java
diff options
context:
space:
mode:
authorNosweh <62889203+Nosweh@users.noreply.github.com>2020-08-28 17:31:40 +0200
committerGitHub <noreply@github.com>2020-08-28 21:01:40 +0530
commit0f0d1994e589b4f2b2603c882f52c79e2307a4f7 (patch)
treeba6d5d5e80f9993ce8d21ea87d2ab687d1675ebb /app/src/main/java
parent88b1de2b509c007171b432da1d9b4b4761509def (diff)
Add Activity to view the Git commit log (#1056)
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt1
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/GitConfigActivity.kt68
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/log/GitCommit.kt18
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/log/GitLogActivity.kt38
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/log/GitLogAdapter.kt56
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/log/GitLogModel.kt53
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt23
7 files changed, 240 insertions, 17 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt
index d9afc7c9..6a0d707c 100644
--- a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt
@@ -48,6 +48,7 @@ import com.zeapo.pwdstore.crypto.BasePgpActivity.Companion.getLongName
import com.zeapo.pwdstore.crypto.DecryptActivity
import com.zeapo.pwdstore.crypto.PasswordCreationActivity
import com.zeapo.pwdstore.git.BaseGitActivity
+import com.zeapo.pwdstore.git.log.GitLogActivity
import com.zeapo.pwdstore.git.GitOperationActivity
import com.zeapo.pwdstore.git.GitServerConfigActivity
import com.zeapo.pwdstore.git.config.AuthMode
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitConfigActivity.kt b/app/src/main/java/com/zeapo/pwdstore/git/GitConfigActivity.kt
index 9205e7fc..89376bfd 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/GitConfigActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/GitConfigActivity.kt
@@ -4,20 +4,24 @@
*/
package com.zeapo.pwdstore.git
+import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.util.Patterns
import androidx.core.os.postDelayed
import androidx.lifecycle.lifecycleScope
+import com.github.ajalt.timberkt.e
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.zeapo.pwdstore.R
import com.zeapo.pwdstore.databinding.ActivityGitConfigBinding
import com.zeapo.pwdstore.git.config.GitSettings
+import com.zeapo.pwdstore.git.log.GitLogActivity
import com.zeapo.pwdstore.utils.PasswordRepository
import com.zeapo.pwdstore.utils.viewBinding
import kotlinx.coroutines.launch
import org.eclipse.jgit.lib.Constants
+import org.eclipse.jgit.lib.Repository
class GitConfigActivity : BaseGitActivity() {
@@ -33,23 +37,7 @@ class GitConfigActivity : BaseGitActivity() {
else
binding.gitUserName.setText(GitSettings.authorName)
binding.gitUserEmail.setText(GitSettings.authorEmail)
- val repo = PasswordRepository.getRepository(PasswordRepository.getRepositoryDirectory())
- if (repo != null) {
- try {
- val objectId = repo.resolve(Constants.HEAD)
- val ref = repo.getRef("refs/heads/${GitSettings.branch}")
- val head = if (ref.objectId.equals(objectId)) ref.name else "DETACHED"
- binding.gitCommitHash.text = String.format("%s (%s)", objectId.abbreviate(8).name(), head)
-
- // enable the abort button only if we're rebasing
- val isRebasing = repo.repositoryState.isRebasing
- binding.gitAbortRebase.isEnabled = isRebasing
- binding.gitAbortRebase.alpha = if (isRebasing) 1.0f else 0.5f
- } catch (ignored: Exception) {
- }
- }
- binding.gitAbortRebase.setOnClickListener { lifecycleScope.launch { launchGitOperation(BREAK_OUT_OF_DETACHED) } }
- binding.gitResetToRemote.setOnClickListener { lifecycleScope.launch { launchGitOperation(REQUEST_RESET) } }
+ setupTools()
binding.saveButton.setOnClickListener {
val email = binding.gitUserEmail.text.toString().trim()
val name = binding.gitUserName.text.toString().trim()
@@ -66,4 +54,50 @@ class GitConfigActivity : BaseGitActivity() {
}
}
}
+
+ /**
+ * Sets up the UI components of the tools section.
+ */
+ private fun setupTools() {
+ val repo = PasswordRepository.getRepository(null)
+ if (repo != null) {
+ binding.gitHeadStatus.text = headStatusMsg(repo)
+ // enable the abort button only if we're rebasing
+ val isRebasing = repo.repositoryState.isRebasing
+ binding.gitAbortRebase.isEnabled = isRebasing
+ binding.gitAbortRebase.alpha = if (isRebasing) 1.0f else 0.5f
+ }
+ binding.gitLog.setOnClickListener {
+ try {
+ intent = Intent(this, GitLogActivity::class.java)
+ startActivity(intent)
+ } catch (ex: Exception) {
+ e(ex) { "Failed to start GitLogActivity" }
+ }
+ }
+ binding.gitAbortRebase.setOnClickListener { lifecycleScope.launch { launchGitOperation(BREAK_OUT_OF_DETACHED) } }
+ binding.gitResetToRemote.setOnClickListener { lifecycleScope.launch { launchGitOperation(REQUEST_RESET) } }
+ }
+
+ /**
+ * Returns a user-friendly message about the current state of HEAD.
+ *
+ * The state is recognized to be either pointing to a branch or detached.
+ */
+ private fun headStatusMsg(repo: Repository): String {
+ return try {
+ val headRef = repo.getRef(Constants.HEAD)
+ if (headRef.isSymbolic) {
+ val branchName = headRef.target.name
+ val shortBranchName = Repository.shortenRefName(branchName)
+ getString(R.string.git_head_on_branch, shortBranchName)
+ } else {
+ val commitHash = headRef.objectId.abbreviate(8).name()
+ getString(R.string.git_head_detached, commitHash)
+ }
+ } catch (ex: Exception) {
+ e(ex) { "Error getting HEAD reference" }
+ getString(R.string.git_head_missing)
+ }
+ }
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/log/GitCommit.kt b/app/src/main/java/com/zeapo/pwdstore/git/log/GitCommit.kt
new file mode 100644
index 00000000..d2425592
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/git/log/GitCommit.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package com.zeapo.pwdstore.git.log
+
+import java.util.Date
+
+/**
+ * Basic information about a git commit.
+ *
+ * @property hash full-length hash of the commit object.
+ * @property shortMessage the commit's short message (i.e. title line).
+ * @property authorName name of the commit's author without email address.
+ * @property time time when the commit was created.
+ */
+data class GitCommit(val hash: String, val shortMessage: String, val authorName: String, val time: Date)
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/log/GitLogActivity.kt b/app/src/main/java/com/zeapo/pwdstore/git/log/GitLogActivity.kt
new file mode 100644
index 00000000..8c1c0f09
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/git/log/GitLogActivity.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package com.zeapo.pwdstore.git.log
+
+import android.os.Bundle
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.zeapo.pwdstore.databinding.ActivityGitLogBinding
+import com.zeapo.pwdstore.git.BaseGitActivity
+import com.zeapo.pwdstore.utils.viewBinding
+
+/**
+ * Displays the repository's git commits in git-log fashion.
+ *
+ * It provides basic information about each commit by way of a non-interactive RecyclerView.
+ */
+class GitLogActivity : BaseGitActivity() {
+
+ private val binding by viewBinding(ActivityGitLogBinding::inflate)
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(binding.root)
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+ createRecyclerView()
+ }
+
+ private fun createRecyclerView() {
+ binding.gitLogRecyclerView.apply {
+ setHasFixedSize(true)
+ addItemDecoration(DividerItemDecoration(context, LinearLayoutManager.VERTICAL))
+ adapter = GitLogAdapter()
+ }
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/log/GitLogAdapter.kt b/app/src/main/java/com/zeapo/pwdstore/git/log/GitLogAdapter.kt
new file mode 100644
index 00000000..a15e7f7e
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/git/log/GitLogAdapter.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package com.zeapo.pwdstore.git.log
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.github.ajalt.timberkt.e
+import com.zeapo.pwdstore.databinding.GitLogRowLayoutBinding
+import java.text.DateFormat
+import java.util.Date
+
+private fun shortHash(hash: String): String {
+ return hash.substring(0 until 8)
+}
+
+private fun stringFrom(date: Date): String {
+ return DateFormat.getDateTimeInstance().format(date)
+}
+
+/**
+ * @see GitLogActivity
+ */
+class GitLogAdapter : RecyclerView.Adapter<GitLogAdapter.ViewHolder>() {
+
+ private val model = GitLogModel()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val inflater = LayoutInflater.from(parent.context)
+ val binding = GitLogRowLayoutBinding.inflate(inflater, parent, false)
+ return ViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
+ val commit = model.get(position)
+ if (commit == null) {
+ e { "There is no git commit for view holder at position $position." }
+ return
+ }
+ viewHolder.bind(commit)
+ }
+
+ override fun getItemCount() = model.size
+
+ class ViewHolder(private val binding: GitLogRowLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
+
+ fun bind(commit: GitCommit) = with(binding) {
+ gitLogRowMessage.text = commit.shortMessage
+ gitLogRowHash.text = shortHash(commit.hash)
+ gitLogRowTime.text = stringFrom(commit.time)
+ }
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/log/GitLogModel.kt b/app/src/main/java/com/zeapo/pwdstore/git/log/GitLogModel.kt
new file mode 100644
index 00000000..22c1ec78
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/git/log/GitLogModel.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package com.zeapo.pwdstore.git.log
+
+import com.github.ajalt.timberkt.e
+import com.zeapo.pwdstore.utils.PasswordRepository
+import com.zeapo.pwdstore.utils.hash
+import com.zeapo.pwdstore.utils.time
+import org.eclipse.jgit.api.Git
+import org.eclipse.jgit.revwalk.RevCommit
+
+private fun commits(): Iterable<RevCommit> {
+ val repo = PasswordRepository.getRepository(null)
+ if (repo == null) {
+ e { "Could not access git repository" }
+ return listOf()
+ }
+ return try {
+ Git(repo).log().call()
+ } catch (exc: Exception) {
+ e(exc) { "Failed to obtain git commits" }
+ listOf()
+ }
+}
+
+/**
+ * Provides [GitCommit]s from a git-log of the password git repository.
+ *
+ * All commits are acquired on the first request to this object.
+ */
+class GitLogModel {
+
+ // All commits are acquired here at once. Acquiring the commits in batches would not have been
+ // entirely sensible because the amount of computation required to obtain commit number n from
+ // the log includes the amount of computation required to obtain commit number n-1 from the log.
+ // This is because the commit graph is walked from HEAD to the last commit to obtain.
+ // Additionally, tests with 1000 commits in the log have not produced a significant delay in the
+ // user experience.
+ private val cache: MutableList<GitCommit> by lazy {
+ commits().map {
+ GitCommit(it.hash, it.shortMessage, it.authorIdent.name, it.time)
+ }.toMutableList()
+ }
+ val size = cache.size
+
+ fun get(index: Int): GitCommit? {
+ if (index >= size) e { "Cannot get git commit with index $index. There are only $size." }
+ return cache.getOrNull(index)
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
index 561b8d99..edc01776 100644
--- a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
@@ -28,6 +28,9 @@ import com.zeapo.pwdstore.git.GitCommandExecutor
import com.zeapo.pwdstore.git.operation.GitOperation
import com.zeapo.pwdstore.utils.PasswordRepository.Companion.getRepositoryDirectory
import java.io.File
+import java.util.Date
+import org.eclipse.jgit.lib.ObjectId
+import org.eclipse.jgit.revwalk.RevCommit
const val OPENPGP_PROVIDER = "org.sufficientlysecure.keychain"
@@ -162,3 +165,23 @@ val Context.autofillManager: AutofillManager?
fun File.isInsideRepository(): Boolean {
return canonicalPath.contains(getRepositoryDirectory().canonicalPath)
}
+
+/**
+ * Unique SHA-1 hash of this commit as hexadecimal string.
+ *
+ * @see RevCommit.id
+ */
+val RevCommit.hash: String
+ get() = ObjectId.toString(id)
+
+/**
+ * Time this commit was made with second precision.
+ *
+ * @see RevCommit.commitTime
+ */
+val RevCommit.time: Date
+ get() {
+ val epochSeconds = commitTime.toLong()
+ val epochMilliseconds = epochSeconds * 1000
+ return Date(epochMilliseconds)
+ }