Skip to content

Commit eb02584

Browse files
ADFA-6094: Initialize git repository (#2063)
* feat(ADFA-6094): Set up repository initialization methods * feat(ADFA-6094): Add repo init button * feat(ADFA-6094): Initialize repo on button click * fix(ADFA-6094): Avoid blocking the main thread * feat(ADFA-6094): Handle initialization errors * test(ADFA-6094): Write unit tests * fix(ADFA-6094): Clean up open repo
1 parent 059a37a commit eb02584

8 files changed

Lines changed: 262 additions & 84 deletions

File tree

‎app/src/main/java/com/itsaky/androidide/fragments/git/GitBottomSheetFragment.kt‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,12 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
291291
}
292292
}
293293

294+
launch {
295+
viewModel.initError.collectLatest {
296+
flashError(getString(R.string.git_init_failed))
297+
}
298+
}
299+
294300
combine(
295301
viewModel.isGitRepository,
296302
viewModel.gitStatus,
@@ -301,7 +307,8 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
301307
!isRepo -> {
302308
binding.apply {
303309
emptyView.visibility = View.VISIBLE
304-
emptyView.text = getString(R.string.not_a_git_repo)
310+
tvRepoStatus.text = getString(R.string.not_a_git_repo)
311+
btnInitRepo.isVisible = true
305312
recyclerView.visibility = View.GONE
306313
cbCheckAll.visibility = View.GONE
307314
commitSection.visibility = View.GONE
@@ -315,7 +322,8 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
315322
allChanges.isEmpty() -> {
316323
binding.apply {
317324
emptyView.visibility = View.VISIBLE
318-
emptyView.text = getString(R.string.no_uncommitted_changes)
325+
btnInitRepo.isVisible = false
326+
tvRepoStatus.text = getString(R.string.no_uncommitted_changes)
319327
recyclerView.visibility = View.GONE
320328
cbCheckAll.visibility = View.VISIBLE
321329
cbCheckAll.isEnabled = false
@@ -333,6 +341,7 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
333341
val hasSelectable = allChanges.hasSelectable()
334342
binding.apply {
335343
emptyView.visibility = View.GONE
344+
btnInitRepo.isVisible = false
336345
recyclerView.visibility = View.VISIBLE
337346
cbCheckAll.visibility = View.VISIBLE
338347
cbCheckAll.isEnabled = hasSelectable
@@ -364,6 +373,8 @@ class GitBottomSheetFragment : Fragment(R.layout.fragment_git_bottom_sheet) {
364373
}
365374

366375
setupPullUI()
376+
377+
binding.btnInitRepo.setOnClickListener { viewModel.initGitRepository() }
367378
}
368379

369380
override fun onResume() {

‎app/src/main/java/com/itsaky/androidide/viewmodel/GitBottomSheetViewModel.kt‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ class GitBottomSheetViewModel(
8686
private val _watermarkError = MutableSharedFlow<Throwable>(extraBufferCapacity = 1)
8787
val watermarkError: SharedFlow<Throwable> = _watermarkError.asSharedFlow()
8888

89+
private val _initError = MutableSharedFlow<Throwable>(extraBufferCapacity = 1)
90+
val initError: SharedFlow<Throwable> = _initError.asSharedFlow()
91+
8992
private var initJob: Job? = null
9093
private var pullResetJob: Job? = null
9194
private var pushResetJob: Job? = null
@@ -184,6 +187,7 @@ class GitBottomSheetViewModel(
184187
val status = repo.getStatus()
185188
_gitStatus.value = status
186189
_currentBranch.value = repo.getCurrentBranch()?.name
190+
_isProjectWatermarkEnabled.value = repo.isCommitWatermarkEnabled()
187191
getLocalCommitsCount()
188192
} catch (e: CancellationException) {
189193
throw e
@@ -762,4 +766,21 @@ class GitBottomSheetViewModel(
762766
}
763767
}
764768
}
769+
770+
fun initGitRepository() {
771+
viewModelScope.launch {
772+
try {
773+
val projectDirPath = IProjectManager.getInstance().projectDirPath
774+
if (projectDirPath.isNotBlank()) {
775+
GitRepositoryManager.initRepository(File(projectDirPath)).use { }
776+
initializeRepository(force = true)
777+
}
778+
} catch (e: CancellationException) {
779+
throw e
780+
} catch (e: Exception) {
781+
log.error("Failed to initialize repository", e)
782+
_initError.tryEmit(e)
783+
}
784+
}
785+
}
765786
}

‎app/src/main/res/layout/fragment_git_bottom_sheet.xml‎

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
<androidx.core.widget.NestedScrollView
33
xmlns:android="http://schemas.android.com/apk/res/android"
44
xmlns:app="http://schemas.android.com/apk/res-auto"
5-
xmlns:tools="http://schemas.android.com/tools"
65
android:layout_width="match_parent"
76
android:layout_height="match_parent"
87
android:fillViewport="true">
@@ -100,18 +99,34 @@
10099
app:layout_constraintStart_toStartOf="parent"
101100
app:layout_constraintTop_toBottomOf="@id/tv_branch_name" />
102101

103-
<TextView
102+
<LinearLayout
104103
android:id="@+id/emptyView"
105-
android:layout_width="0dp"
106-
android:layout_height="0dp"
107-
android:gravity="center"
108-
android:text="@string/no_uncommitted_changes"
109-
android:textAppearance="?attr/textAppearanceBody2"
104+
android:layout_width="match_parent"
105+
android:layout_height="wrap_content"
106+
android:orientation="vertical"
110107
android:visibility="gone"
111108
app:layout_constraintBottom_toBottomOf="parent"
112109
app:layout_constraintEnd_toEndOf="parent"
113110
app:layout_constraintStart_toStartOf="parent"
114-
app:layout_constraintTop_toBottomOf="@id/cbCheckAll" />
111+
app:layout_constraintTop_toBottomOf="@id/cbCheckAll">
112+
113+
<TextView
114+
android:id="@+id/tvRepoStatus"
115+
android:layout_width="match_parent"
116+
android:layout_height="wrap_content"
117+
android:gravity="center"
118+
android:text="@string/no_uncommitted_changes"
119+
android:textAppearance="?attr/textAppearanceBody2" />
120+
121+
<com.google.android.material.button.MaterialButton
122+
android:id="@+id/btn_init_repo"
123+
style="@style/Widget.Material3.Button.OutlinedButton"
124+
android:layout_width="wrap_content"
125+
android:layout_height="wrap_content"
126+
android:layout_gravity="center"
127+
android:layout_marginTop="8dp"
128+
android:text="@string/initialize_repository" />
129+
</LinearLayout>
115130

116131
<androidx.recyclerview.widget.RecyclerView
117132
android:id="@+id/recyclerView"

‎app/src/test/java/com/itsaky/androidide/viewmodel/GitBottomSheetViewModelTest.kt‎

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ package com.itsaky.androidide.viewmodel
33
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
44
import com.itsaky.androidide.git.core.GitCredentialsManager
55
import com.itsaky.androidide.git.core.GitRepository
6+
import com.itsaky.androidide.git.core.GitRepositoryManager
67
import com.itsaky.androidide.git.core.models.GitBranch
8+
import com.itsaky.androidide.projects.IProjectManager
79
import io.mockk.coEvery
810
import io.mockk.coVerify
911
import io.mockk.every
1012
import io.mockk.mockk
13+
import io.mockk.mockkObject
1114
import io.mockk.unmockkAll
1215
import kotlinx.coroutines.ExperimentalCoroutinesApi
1316
import kotlinx.coroutines.delay
@@ -24,6 +27,7 @@ import org.junit.Rule
2427
import org.junit.Test
2528
import org.junit.runner.RunWith
2629
import org.junit.runners.JUnit4
30+
import java.io.File
2731
import java.io.IOException
2832
import kotlin.time.Duration.Companion.milliseconds
2933

@@ -226,18 +230,16 @@ class GitBottomSheetViewModelTest {
226230
@Test
227231
fun `initializeRepository clears currentRepository when opening fails`() =
228232
runTest {
229-
io.mockk.mockkObject(com.itsaky.androidide.projects.IProjectManager.Companion)
230-
val mockProjectManager = mockk<com.itsaky.androidide.projects.IProjectManager>(relaxed = true)
233+
mockkObject(IProjectManager.Companion)
234+
val mockProjectManager = mockk<IProjectManager>(relaxed = true)
231235
every { mockProjectManager.projectDirPath } returns "/mock/path"
232236
every {
233-
com.itsaky.androidide.projects.IProjectManager
234-
.getInstance()
237+
IProjectManager.getInstance()
235238
} returns mockProjectManager
236239

237-
io.mockk.mockkObject(com.itsaky.androidide.git.core.GitRepositoryManager)
240+
mockkObject(GitRepositoryManager)
238241
coEvery {
239-
com.itsaky.androidide.git.core.GitRepositoryManager
240-
.openRepository(any())
242+
GitRepositoryManager.openRepository(any())
241243
} throws RuntimeException("Corrupt repository")
242244

243245
viewModel.initializeRepository(force = true)
@@ -312,7 +314,42 @@ class GitBottomSheetViewModelTest {
312314

313315
advanceUntilIdle()
314316

315-
assertEquals(true, viewModel.isProjectWatermarkEnabled.value)
317+
org.junit.Assert.assertEquals(true, viewModel.isProjectWatermarkEnabled.value)
316318
coVerify(exactly = 1) { repository.setCommitWatermarkEnabled(true) }
317319
}
320+
321+
@Test
322+
fun `initGitRepository delegates to GitRepositoryManager and calls initializeRepository`() =
323+
runTest {
324+
mockkObject(IProjectManager.Companion)
325+
val mockProjectManager = mockk<IProjectManager>(relaxed = true)
326+
every { mockProjectManager.projectDirPath } returns "/mock/path"
327+
every { IProjectManager.getInstance() } returns mockProjectManager
328+
329+
mockkObject(GitRepositoryManager)
330+
coEvery { GitRepositoryManager.initRepository(any()) } returns mockk(relaxed = true)
331+
332+
viewModel.initGitRepository()
333+
advanceUntilIdle()
334+
335+
coVerify { GitRepositoryManager.initRepository(File("/mock/path")) }
336+
}
337+
338+
@Test
339+
fun `initGitRepository handles exceptions smoothly`() =
340+
runTest {
341+
mockkObject(IProjectManager.Companion)
342+
val mockProjectManager = mockk<IProjectManager>(relaxed = true)
343+
every { mockProjectManager.projectDirPath } returns "/mock/path"
344+
every { IProjectManager.getInstance() } returns mockProjectManager
345+
346+
mockkObject(GitRepositoryManager)
347+
coEvery { GitRepositoryManager.initRepository(any()) } throws RuntimeException("Init failed")
348+
349+
// Should not throw
350+
viewModel.initGitRepository()
351+
advanceUntilIdle()
352+
353+
coVerify { GitRepositoryManager.initRepository(File("/mock/path")) }
354+
}
318355
}

‎git-core/build.gradle.kts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ dependencies {
1818
implementation(libs.androidx.lifecycle.viewmodel.ktx)
1919
implementation(libs.androidx.security.crypto)
2020

21-
testImplementation(libs.tests.junit)
22-
androidTestImplementation(libs.tests.androidx.junit)
23-
androidTestImplementation(libs.tests.androidx.espresso.core)
21+
testImplementation(libs.tests.junit)
22+
testImplementation(libs.tests.kotlinx.coroutines)
23+
testImplementation(libs.tests.mockk)
24+
androidTestImplementation(libs.tests.androidx.junit)
25+
androidTestImplementation(libs.tests.androidx.espresso.core)
2426
}

0 commit comments

Comments
 (0)