Skip to content

ADFA-2882: Clone git repo#1005

Merged
dara-abijo-adfa merged 22 commits intostagefrom
ADFA-2882-clone-git-repo
Mar 4, 2026
Merged

ADFA-2882: Clone git repo#1005
dara-abijo-adfa merged 22 commits intostagefrom
ADFA-2882-clone-git-repo

Conversation

@dara-abijo-adfa
Copy link
Contributor

@dara-abijo-adfa dara-abijo-adfa commented Feb 23, 2026

  • Clone existing Git repository
  • Show cloning progress
  • Handle errors
  • Open the project in the editor
Screen_recording_20260306_181942.mp4

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a "clone repository" feature: UI fragment and layout, CloneRepositoryViewModel and CloneRepoUiState, git-core cloneRepository API, strings/icon, build dependency updates, main-screen action/navigation wiring, and unit tests including a MainDispatcherRule.

Changes

Cohort / File(s) Summary
App build
app/build.gradle.kts, gradle/libs.versions.toml
Added gitCore dependency and test coroutine library entry; added coroutine test dependency to app tests.
Main activity & layout
app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt, app/src/main/res/layout/activity_main.xml, app/src/main/java/com/itsaky/androidide/viewmodel/MainViewModel.kt
Introduced SCREEN_CLONE_REPO, added fragment container clone_repository_view, and wired fragment visibility for the new screen.
Main screen actions
app/src/main/java/com/itsaky/androidide/fragments/MainFragment.kt, app/src/main/java/com/itsaky/androidide/models/MainScreenAction.kt
Added ACTION_CLONE_REPO action, cloneRepo action entry, and handler to navigate to clone screen.
Clone UI & layout
app/src/main/java/com/itsaky/androidide/fragments/CloneRepositoryFragment.kt, app/src/main/res/layout/fragment_clone_repository.xml
New fragment and layout: URL/path inputs, folder picker, optional auth fields, progress UI, and clone button with handlers.
ViewModel & UI state
app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt, git-core/src/main/java/com/itsaky/androidide/git/core/models/CloneRepoUiState.kt
New ViewModel managing validation, coroutine clone flow, progress reporting, cleanup; new sealed CloneRepoUiState representing Idle/Cloning/Success/Error.
Git core
git-core/src/main/java/com/itsaky/androidide/git/core/GitRepositoryManager.kt, git-core/build.gradle.kts
Added suspend cloneRepository(...) performing JGit clone with optional credentials and progress monitor; added lifecycle dependency.
Resources
resources/src/main/res/values/strings.xml, resources/src/main/res/drawable/ic_clone_repo.xml
Added Git-related strings and a new clone icon drawable.
Tests & test utilities
app/src/test/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModelTest.kt, app/src/test/java/com/itsaky/androidide/viewmodel/MainDispatcherRule.kt
Added comprehensive unit tests for CloneRepositoryViewModel and MainDispatcherRule to control Dispatchers.Main in tests.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant Fragment as CloneRepositoryFragment
    participant ViewModel as CloneRepositoryViewModel
    participant Manager as GitRepositoryManager
    participant JGit as JGit

    User->>Fragment: Enter URL & local path (toggle auth)
    Fragment->>ViewModel: onInputChanged(url, path)
    ViewModel-->>Fragment: emit state (enable/disable clone button)

    User->>Fragment: Click Clone
    Fragment->>ViewModel: cloneRepository(url, path, username?, token?)
    ViewModel->>ViewModel: set loading state, emit Cloning
    ViewModel->>Manager: cloneRepository(url, destDir, credentials?, progressMonitor)
    Manager->>JGit: Git.cloneRepository() executes clone
    JGit-->>Manager: report progress/completion
    Manager-->>ViewModel: return repository or throw
    ViewModel-->>Fragment: emit progress and Success/Error state
    Fragment->>User: navigate/open project on success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • jatezzz
  • Daniel-ADFA
  • itsaky-adfa

Poem

🐇
I hopped through branches, sniffed the URL bright,
Picked a folder, toggled auth by moonlight.
Progress bars blink, the clone hums low,
A project lands — down the burrow we go. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding Git repository cloning functionality to the application.
Description check ✅ Passed The description is directly related to the changeset, detailing the key features implemented: cloning repos, showing progress, handling errors, and opening projects.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ADFA-2882-clone-git-repo

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt`:
- Around line 36-129: The cloneRepository flow can leave a partially populated
destDir when cloning fails, preventing retries; modify cloneRepository to track
whether this invocation created or populated the destination (e.g., set a
boolean like createdOrPopulatedDest before calling
GitRepositoryManager.cloneRepository), ensure you only attempt to remove the
directory if this operation created/populated it and the clone did not succeed,
and on catch/failure delete the destDir contents (recursively) to clean up
partial files so subsequent attempts pass the pre-check; reference
cloneRepository, destDir, GitRepositoryManager.cloneRepository and the
try/catch/finally block when adding the created flag and cleanup logic.
- Around line 131-155: The updateState function's use of the Elvis operator
prevents callers from clearing nullable fields (e.g., statusResId) because
passing null falls back to the previous value; change updateState to accept an
explicit tri-state for nullable fields so callers can set, keep, or clear values
(for example, add boolean "set" flags like statusResIdSet: Boolean = false or
statusResIdProvided: Boolean = false for each nullable field and update the copy
logic to: if (statusResIdProvided) use the provided value (which may be null)
else keep uiState.value.statusResId). Update call sites such as updateProgressUI
(which calls updateState(statusResId = null, statusMessage = "")) to pass the
corresponding "Provided"/"Set" flag so null can clear the field; keep existing
direct copies (it.copy(statusResId = null)) unchanged if they already work.

In `@app/src/main/res/layout/fragment_clone_repository.xml`:
- Around line 105-109: The auth Group (androidx.constraintlayout.widget.Group
with id authGroup referencing usernameLayout and passwordLayout) needs to be
hidden by default; add the android:visibility="gone" attribute to the Group
element so the username/password layouts are not visible until the user opts in.
- Around line 48-50: Replace the hardcoded app:endIconContentDescription="Pick
folder" in fragment_clone_repository.xml with a string resource reference (e.g.,
`@string/pick_folder`) and add a new entry named pick_folder to
resources/src/main/res/values/strings.xml containing the localized text "Pick
folder" (and translations as needed) so the accessibility label is externalized
for localization and consistency.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 081a638 and d9b664d.

📒 Files selected for processing (14)
  • app/build.gradle.kts
  • app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt
  • app/src/main/java/com/itsaky/androidide/fragments/CloneRepositoryFragment.kt
  • app/src/main/java/com/itsaky/androidide/fragments/MainFragment.kt
  • app/src/main/java/com/itsaky/androidide/models/MainScreenAction.kt
  • app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt
  • app/src/main/java/com/itsaky/androidide/viewmodel/MainViewModel.kt
  • app/src/main/res/layout/activity_main.xml
  • app/src/main/res/layout/fragment_clone_repository.xml
  • git-core/build.gradle.kts
  • git-core/src/main/java/com/itsaky/androidide/git/core/GitRepositoryManager.kt
  • git-core/src/main/java/com/itsaky/androidide/git/core/models/CloneRepoUiState.kt
  • resources/src/main/res/drawable/ic_clone_repo.xml
  • resources/src/main/res/values/strings.xml

Replace the updateState helper function with a direct update of state in order to avoid boilerplate especially if the state object gets larger.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt (3)

29-29: Use it.isLoading instead of uiState.value.isLoading inside the update lambda.

The _uiState.update { } lambda receives the current atomic snapshot as it. Reading uiState.value.isLoading is a separate, non-atomic read from the same MutableStateFlow, introducing a subtle TOCTOU window if the state is updated concurrently (e.g., cloneRepository sets isLoading = true between the outer update read and the lambda execution).

♻️ Proposed fix
-                isCloneButtonEnabled = url.isNotBlank() && path.isNotBlank() && !uiState.value.isLoading
+                isCloneButtonEnabled = url.isNotBlank() && path.isNotBlank() && !it.isLoading
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt`
at line 29, Inside the _uiState.update { } lambda in CloneRepositoryViewModel,
replace the non-atomic read of uiState.value.isLoading with the lambda parameter
it.isLoading when computing isCloneButtonEnabled (the update block that sets
isCloneButtonEnabled = url.isNotBlank() && path.isNotBlank() &&
!uiState.value.isLoading should use it.isLoading instead) so the check is atomic
with the snapshot provided to the update call.

72-72: Empty ProgressMonitor method bodies flagged by Detekt.

start(), endTask(), and showDuration() are all empty no-op implementations. Detekt's EmptyFunctionBlock rule flags these. Since these are intentional no-ops, add a comment or collapse to expression-body form to silence the warnings:

♻️ Proposed fix
-                    override fun start(totalTasks: Int) {}
+                    override fun start(totalTasks: Int) = Unit

...

-                    override fun endTask() {}
+                    override fun endTask() = Unit

...

-                    override fun showDuration(enabled: Boolean) {}
+                    override fun showDuration(enabled: Boolean) = Unit

Also applies to: 86-86, 90-90

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt`
at line 72, The Detekt warnings come from empty ProgressMonitor implementations;
update the no-op methods start(totalTasks: Int), endTask(), and
showDuration(durationMs: Long) in CloneRepositoryViewModel to explicitly mark
them as intentional no-ops (either add a single-line comment like "// no-op
(intentional)" inside each body or convert them to expression-body form
returning Unit) so Detekt's EmptyFunctionBlock rule is satisfied while
preserving behavior.

88-88: isCancelled() always returns false — clone cannot be cooperatively cancelled.

JGit polls isCancelled() in tight loops during the clone operation. With a hard-coded false, the JGit operation will run to completion (or network failure) even after the viewModelScope is cancelled (e.g., user navigates away). Consider tying it to the coroutine's isActive state, which is accessible via closure inside the launch block:

♻️ Proposed fix
-                    override fun isCancelled(): Boolean = false
+                    override fun isCancelled(): Boolean = !isActive

This allows JGit to stop mid-clone when the ViewModel is cleared, avoiding unnecessary network usage and leaving cleanup to the finally block which still runs on cancellation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt`
at line 88, The ProgressMonitor's override fun isCancelled() currently always
returns false, preventing cooperative cancellation; inside the coroutine
launched on viewModelScope (the launch block in CloneRepositoryViewModel),
change the ProgressMonitor implementation so isCancelled() returns the
coroutine's active state (e.g., return !isActive) by creating the monitor inside
the launch closure so it can read isActive; this ties JGit's polling to the
coroutine cancellation and allows the clone to stop mid-operation while still
running your existing cleanup in the finally block.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt`:
- Around line 136-141: The finally block in the coroutine (where
hasCloned/isExistingDir/destDir are used) performs synchronous
destDir.deleteRecursively() on Dispatchers.Main; move the deletion work onto
Dispatchers.IO by wrapping the cleanup in withContext(Dispatchers.IO) (or
launching a separate IO coroutine) so both branches (destDir.deleteRecursively()
and destDir.listFiles()?.forEach { it.deleteRecursively() }) run off the main
thread; update the cleanup in the finally that references
hasCloned/isExistingDir/destDir accordingly to perform IO-dispatcher-safe
deletion.

---

Nitpick comments:
In
`@app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt`:
- Line 29: Inside the _uiState.update { } lambda in CloneRepositoryViewModel,
replace the non-atomic read of uiState.value.isLoading with the lambda parameter
it.isLoading when computing isCloneButtonEnabled (the update block that sets
isCloneButtonEnabled = url.isNotBlank() && path.isNotBlank() &&
!uiState.value.isLoading should use it.isLoading instead) so the check is atomic
with the snapshot provided to the update call.
- Line 72: The Detekt warnings come from empty ProgressMonitor implementations;
update the no-op methods start(totalTasks: Int), endTask(), and
showDuration(durationMs: Long) in CloneRepositoryViewModel to explicitly mark
them as intentional no-ops (either add a single-line comment like "// no-op
(intentional)" inside each body or convert them to expression-body form
returning Unit) so Detekt's EmptyFunctionBlock rule is satisfied while
preserving behavior.
- Line 88: The ProgressMonitor's override fun isCancelled() currently always
returns false, preventing cooperative cancellation; inside the coroutine
launched on viewModelScope (the launch block in CloneRepositoryViewModel),
change the ProgressMonitor implementation so isCancelled() returns the
coroutine's active state (e.g., return !isActive) by creating the monitor inside
the launch closure so it can read isActive; this ties JGit's polling to the
coroutine cancellation and allows the clone to stop mid-operation while still
running your existing cleanup in the finally block.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d9b664d and a445c61.

📒 Files selected for processing (3)
  • app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt
  • app/src/main/res/layout/fragment_clone_repository.xml
  • resources/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/res/layout/fragment_clone_repository.xml

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
app/src/main/java/com/itsaky/androidide/fragments/MainFragment.kt (1)

65-65: Add long-press tooltip mapping for clone action.

Line 65 introduces ACTION_CLONE_REPO, but getToolTipTagForAction currently has no matching case, so long-press on this action won’t show contextual help like the others.

Suggested follow-up patch
 private fun getToolTipTagForAction(id: Int): String =
 	when (id) {
 		ACTION_CREATE_PROJECT -> PROJECT_NEW
 		ACTION_OPEN_PROJECT -> PROJECT_OPEN
+		ACTION_CLONE_REPO -> MAIN_CLONE_REPO
 		ACTION_DELETE_PROJECT -> MAIN_PROJECT_DELETE
 		ACTION_DOCS -> MAIN_HELP
         ACTION_OPEN_TERMINAL -> MAIN_TERMINAL
         ACTION_PREFERENCES -> MAIN_PREFERENCES
 		else -> ""
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/itsaky/androidide/fragments/MainFragment.kt` at line
65, getToolTipTagForAction is missing a case for ACTION_CLONE_REPO, so
long-press tooltips won't appear for the clone action; update
getToolTipTagForAction to return the appropriate tooltip tag (same pattern as
other actions) when the action equals ACTION_CLONE_REPO (the method that
dispatches ACTION_CLONE_REPO to showCloneRepository should then get a matching
tooltip), ensuring the returned string matches the resource/key used for clone
action tooltips.
app/src/test/java/com/itsaky/androidide/viewmodel/MainDispatcherRule.kt (1)

15-16: Prefer StandardTestDispatcher as the default for deterministic tests.

Using UnconfinedTestDispatcher by default can hide ordering/race issues in coroutine tests. Consider using StandardTestDispatcher and letting tests explicitly advance the scheduler.

Proposed change
 import kotlinx.coroutines.test.TestDispatcher
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.StandardTestDispatcher
@@
 class MainDispatcherRule(
-    private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
+    private val testDispatcher: TestDispatcher = StandardTestDispatcher()
 ) : TestWatcher() {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/test/java/com/itsaky/androidide/viewmodel/MainDispatcherRule.kt`
around lines 15 - 16, The test rule currently instantiates testDispatcher as
UnconfinedTestDispatcher in MainDispatcherRule; replace that with
StandardTestDispatcher to make coroutine scheduling deterministic (so tests must
explicitly advance the scheduler). Update the declaration of the testDispatcher
property in the MainDispatcherRule class to use StandardTestDispatcher and
ensure any tests that relied on automatic execution now advance the dispatcher
via testDispatcher.scheduler or appropriate advanceUntilIdle/advanceTimeBy
calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/test/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModelTest.kt`:
- Around line 119-183: Wrap each test that calls viewModel.cloneRepository in a
kotlinx.coroutines.test.runTest { ... } block and after calling
viewModel.cloneRepository(...) call advanceUntilIdle() before reading
viewModel.uiState or doing coVerify; specifically update the tests that invoke
viewModel.cloneRepository (the success, failure, and auth tests) to run inside
runTest and call advanceUntilIdle() to ensure the viewModelScope.launch
coroutine completes before assertions and coVerify against
GitRepositoryManager.cloneRepository.

In `@gradle/libs.versions.toml`:
- Line 293: The version entry for the tests-kotlinx-coroutines library is using
a literal key (`version = "kotlinxCoroutinesCore"`) instead of referencing the
named version from the [versions] block; change that to use a version reference
by replacing the assignment with `version.ref = "kotlinxCoroutinesCore"` for the
tests-kotlinx-coroutines entry so Gradle resolves the named version (look for
the tests-kotlinx-coroutines line and the `kotlinxCoroutinesCore` version name).

---

Nitpick comments:
In `@app/src/main/java/com/itsaky/androidide/fragments/MainFragment.kt`:
- Line 65: getToolTipTagForAction is missing a case for ACTION_CLONE_REPO, so
long-press tooltips won't appear for the clone action; update
getToolTipTagForAction to return the appropriate tooltip tag (same pattern as
other actions) when the action equals ACTION_CLONE_REPO (the method that
dispatches ACTION_CLONE_REPO to showCloneRepository should then get a matching
tooltip), ensuring the returned string matches the resource/key used for clone
action tooltips.

In `@app/src/test/java/com/itsaky/androidide/viewmodel/MainDispatcherRule.kt`:
- Around line 15-16: The test rule currently instantiates testDispatcher as
UnconfinedTestDispatcher in MainDispatcherRule; replace that with
StandardTestDispatcher to make coroutine scheduling deterministic (so tests must
explicitly advance the scheduler). Update the declaration of the testDispatcher
property in the MainDispatcherRule class to use StandardTestDispatcher and
ensure any tests that relied on automatic execution now advance the dispatcher
via testDispatcher.scheduler or appropriate advanceUntilIdle/advanceTimeBy
calls.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a445c61 and 73567ba.

📒 Files selected for processing (7)
  • app/build.gradle.kts
  • app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt
  • app/src/main/java/com/itsaky/androidide/fragments/MainFragment.kt
  • app/src/test/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModelTest.kt
  • app/src/test/java/com/itsaky/androidide/viewmodel/MainDispatcherRule.kt
  • gradle/libs.versions.toml
  • resources/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt
  • resources/src/main/res/values/strings.xml
  • app/build.gradle.kts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt (1)

74-74: Remove empty start / endTask / showDuration bodies to satisfy detekt.

All three are flagged by detekt.empty-blocks.EmptyFunctionBlock. Since the interface default (or no-op) is the intent, the bodies can simply be removed or replaced with a single-line expression form.

♻️ Proposed fix
-                    override fun start(totalTasks: Int) {}
+                    override fun start(totalTasks: Int) = Unit

                     override fun beginTask(title: String, totalWork: Int) { ... }

                     override fun update(completed: Int) { ... }

-                    override fun endTask() {}
+                    override fun endTask() = Unit

                     override fun isCancelled(): Boolean = false

-                    override fun showDuration(enabled: Boolean) {}
+                    override fun showDuration(enabled: Boolean) = Unit
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt`
at line 74, Remove the empty function bodies for the no-op overrides in
CloneRepositoryViewModel (the methods start(totalTasks: Int), endTask(), and
showDuration()) to satisfy detekt.empty-blocks.EmptyFunctionBlock; either delete
the overrides entirely if the interface supplies defaults or change each to a
single-expression no-op (e.g., use = Unit) so the methods remain but have a
one-line expression form.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt`:
- Around line 26-34: In onInputChanged, update the atomic StateFlow lambda to
use the snapshot parameter instead of reading the flow externally: inside the
_uiState.update { ... } block replace the external uiState.value.isLoading check
with the lambda receiver `it.isLoading` so isCloneButtonEnabled is computed from
the current snapshot; modify the code in onInputChanged accordingly (referencing
_uiState.update and isCloneButtonEnabled).
- Around line 69-113: The ProgressMonitor implementation uses unsynchronized
mutable fields totalWork, completedWork and currentTaskTitle that JGit may
access from worker threads; make these thread-safe by replacing totalWork and
completedWork with AtomicInteger (or mark them `@Volatile` and use atomic
operations) and currentTaskTitle with an AtomicReference<String> (or volatile)
and update beginTask, update and updateProgressUI to read/write the atomic vars
instead of the plain vars so all reads/writes have proper visibility before
calling _uiState.update.

---

Nitpick comments:
In
`@app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt`:
- Line 74: Remove the empty function bodies for the no-op overrides in
CloneRepositoryViewModel (the methods start(totalTasks: Int), endTask(), and
showDuration()) to satisfy detekt.empty-blocks.EmptyFunctionBlock; either delete
the overrides entirely if the interface supplies defaults or change each to a
single-expression no-op (e.g., use = Unit) so the methods remain but have a
one-line expression form.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 73567ba and ddfb2eb.

📒 Files selected for processing (1)
  • app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt (1)

85-132: ⚠️ Potential issue | 🟡 Minor

ProgressMonitor fields lack thread-safety guarantees.

totalWork, completedWork, and currentTaskTitle are plain var fields accessed from JGit worker threads (in beginTask/update) and read in updateProgressUI. Without @Volatile or atomic types, concurrent writes and reads may produce stale or torn values.

🔒 Proposed fix using `@Volatile`
                 val progressMonitor = object : ProgressMonitor {
-                    private var totalWork = 0
-                    private var completedWork = 0
-                    private var currentTaskTitle = ""
+                    `@Volatile` private var totalWork = 0
+                    `@Volatile` private var completedWork = 0
+                    `@Volatile` private var currentTaskTitle = ""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt`
around lines 85 - 132, The ProgressMonitor anonymous object uses non-thread-safe
mutable fields totalWork, completedWork and currentTaskTitle accessed from JGit
worker threads (beginTask/update) and read in updateProgressUI; make these
variables thread-safe (e.g. mark primitive/counter fields as `@Volatile` or
replace with AtomicInteger/AtomicReference<String> as appropriate) and update
beginTask/update/updateProgressUI to use the chosen atomic/volatile types to
avoid races when updating _uiState in CloneRepositoryViewModel's progress
monitor.
🧹 Nitpick comments (4)
app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt (2)

90-108: Empty function blocks are intentional but could be documented.

The start(), endTask(), and showDuration() overrides are required by the ProgressMonitor interface but not needed for this use case. Consider adding a brief comment or using Unit explicitly to signal intent and suppress static analysis warnings.

♻️ Optional: Document intentional no-ops
-                    override fun start(totalTasks: Int) {}
+                    override fun start(totalTasks: Int) = Unit // Not needed for UI progress
 
                     // ... other methods ...
 
-                    override fun endTask() {}
+                    override fun endTask() = Unit // Progress resets on next beginTask
 
-                    override fun showDuration(enabled: Boolean) {}
+                    override fun showDuration(enabled: Boolean) = Unit // Not displaying duration
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt`
around lines 90 - 108, The overrides start(), endTask(), and
showDuration(enabled: Boolean) in the ProgressMonitor implementation of
CloneRepositoryViewModel are intentionally no-ops but lack documentation; update
these methods (start, endTask, showDuration) to either use an explicit Unit body
or add a brief comment like "// no-op: not needed for this monitor" to signal
intent and suppress static analysis warnings, leaving the existing logic in
beginTask, update, isCancelled, and updateProgressUI unchanged.

26-45: Use the lambda parameter it inside StateFlow.update for consistency.

Lines 29-35 and 123-131 capture currentState before calling update, then use currentState.copy() inside the lambda. While functionally correct here (single writer), the idiomatic pattern is to use the lambda parameter it which represents the atomic snapshot. This also avoids potential issues if the code evolves to have concurrent writers.

♻️ Proposed fix for onInputChanged
     fun onInputChanged(url: String, path: String) {
-        val currentState = _uiState.value
-        if (currentState is CloneRepoUiState.Idle) {
-            _uiState.update {
-                currentState.copy(
+        _uiState.update { currentState ->
+            when (currentState) {
+                is CloneRepoUiState.Idle -> currentState.copy(
                     url = url,
                     localPath = path,
                     isCloneButtonEnabled = url.isNotBlank() && path.isNotBlank()
                 )
-            }
-        } else if (currentState is CloneRepoUiState.Error) {
-            _uiState.update {
-                CloneRepoUiState.Idle(
+                is CloneRepoUiState.Error -> CloneRepoUiState.Idle(
                     url = url,
                     localPath = path,
                     isCloneButtonEnabled = url.isNotBlank() && path.isNotBlank()
                 )
+                else -> currentState
             }
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt`
around lines 26 - 45, The update lambdas in onInputChanged use the captured
currentState instead of the atomic lambda parameter; change both _uiState.update
{ currentState.copy(...) } and the Error branch _uiState.update {
CloneRepoUiState.Idle(...) } to use the lambda parameter (it) inside
_uiState.update (e.g., _uiState.update { it.copy(...) } or _uiState.update {
CloneRepoUiState.Idle(...) } using values from the lambda) so the code uses the
provided snapshot atomically; update occurrences in onInputChanged and any
similar update calls that reference currentState.
app/src/test/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModelTest.kt (1)

35-211: Consider adding tests for progress updates and state transition edge cases.

The test suite covers the main flows well. For completeness, consider adding tests for:

  • Progress updates reaching the UI during cloning (verify Cloning state with non-zero clonePercentage)
  • onInputChanged called while in Cloning state (should be no-op)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/test/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModelTest.kt`
around lines 35 - 211, Add two tests: one that verifies progress updates flow
into uiState by mocking GitRepositoryManager.cloneRepository to capture the
progressMonitor argument passed to CloneRepositoryViewModel.cloneRepository,
simulate progress callbacks (e.g., call progressMonitor.beginTask/update and set
non-zero percentage) and assert uiState becomes CloneRepoUiState.Cloning with
clonePercentage > 0 and transitions to Success when complete; and one that
verifies onInputChanged is a no-op during cloning by starting a mocked clone
(capture progressMonitor but don't complete), call viewModel.onInputChanged(...)
while uiState is CloneRepoUiState.Cloning, and assert the
viewModel.uiState.url/path/clonePercentage remain unchanged. Reference
CloneRepositoryViewModel.cloneRepository,
CloneRepositoryViewModel.onInputChanged, viewModel.uiState, and
CloneRepoUiState.Cloning/Success to locate where to hook/verify behavior.
git-core/src/main/java/com/itsaky/androidide/git/core/models/CloneRepoUiState.kt (1)

24-29: Consider enforcing that at least one error descriptor is provided.

Both errorMessage and errorResId are nullable, which could result in an Error state with no displayable message. Consider using a sealed class for the error content or adding validation.

♻️ Optional: Use a sealed class for error content
     data class Error(
         val url: String,
         val localPath: String,
-        val errorMessage: String? = null,
-        `@StringRes` val errorResId: Int? = null,
+        val error: ErrorContent
-    ) : CloneRepoUiState
+    ) : CloneRepoUiState {
+        sealed interface ErrorContent {
+            data class Message(val message: String) : ErrorContent
+            data class Resource(`@StringRes` val resId: Int) : ErrorContent
+        }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@git-core/src/main/java/com/itsaky/androidide/git/core/models/CloneRepoUiState.kt`
around lines 24 - 29, The Error data class in CloneRepoUiState currently allows
both errorMessage and errorResId to be null; change the model so an Error always
carries displayable content by either (a) replacing the two nullable fields with
a sealed hierarchy (e.g. Error.Message(String) and Error.Resource(`@StringRes`
Int)) under CloneRepoUiState.Error, or (b) keep the data class but add
validation in its constructor (init block) to throw IllegalArgumentException if
both errorMessage and errorResId are null; update any call sites that construct
CloneRepoUiState.Error (referencing the Error data class, errorMessage and
errorResId) to provide one of the two variants/fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt`:
- Around line 85-132: The ProgressMonitor anonymous object uses non-thread-safe
mutable fields totalWork, completedWork and currentTaskTitle accessed from JGit
worker threads (beginTask/update) and read in updateProgressUI; make these
variables thread-safe (e.g. mark primitive/counter fields as `@Volatile` or
replace with AtomicInteger/AtomicReference<String> as appropriate) and update
beginTask/update/updateProgressUI to use the chosen atomic/volatile types to
avoid races when updating _uiState in CloneRepositoryViewModel's progress
monitor.

---

Nitpick comments:
In
`@app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt`:
- Around line 90-108: The overrides start(), endTask(), and
showDuration(enabled: Boolean) in the ProgressMonitor implementation of
CloneRepositoryViewModel are intentionally no-ops but lack documentation; update
these methods (start, endTask, showDuration) to either use an explicit Unit body
or add a brief comment like "// no-op: not needed for this monitor" to signal
intent and suppress static analysis warnings, leaving the existing logic in
beginTask, update, isCancelled, and updateProgressUI unchanged.
- Around line 26-45: The update lambdas in onInputChanged use the captured
currentState instead of the atomic lambda parameter; change both _uiState.update
{ currentState.copy(...) } and the Error branch _uiState.update {
CloneRepoUiState.Idle(...) } to use the lambda parameter (it) inside
_uiState.update (e.g., _uiState.update { it.copy(...) } or _uiState.update {
CloneRepoUiState.Idle(...) } using values from the lambda) so the code uses the
provided snapshot atomically; update occurrences in onInputChanged and any
similar update calls that reference currentState.

In
`@app/src/test/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModelTest.kt`:
- Around line 35-211: Add two tests: one that verifies progress updates flow
into uiState by mocking GitRepositoryManager.cloneRepository to capture the
progressMonitor argument passed to CloneRepositoryViewModel.cloneRepository,
simulate progress callbacks (e.g., call progressMonitor.beginTask/update and set
non-zero percentage) and assert uiState becomes CloneRepoUiState.Cloning with
clonePercentage > 0 and transitions to Success when complete; and one that
verifies onInputChanged is a no-op during cloning by starting a mocked clone
(capture progressMonitor but don't complete), call viewModel.onInputChanged(...)
while uiState is CloneRepoUiState.Cloning, and assert the
viewModel.uiState.url/path/clonePercentage remain unchanged. Reference
CloneRepositoryViewModel.cloneRepository,
CloneRepositoryViewModel.onInputChanged, viewModel.uiState, and
CloneRepoUiState.Cloning/Success to locate where to hook/verify behavior.

In
`@git-core/src/main/java/com/itsaky/androidide/git/core/models/CloneRepoUiState.kt`:
- Around line 24-29: The Error data class in CloneRepoUiState currently allows
both errorMessage and errorResId to be null; change the model so an Error always
carries displayable content by either (a) replacing the two nullable fields with
a sealed hierarchy (e.g. Error.Message(String) and Error.Resource(`@StringRes`
Int)) under CloneRepoUiState.Error, or (b) keep the data class but add
validation in its constructor (init block) to throw IllegalArgumentException if
both errorMessage and errorResId are null; update any call sites that construct
CloneRepoUiState.Error (referencing the Error data class, errorMessage and
errorResId) to provide one of the two variants/fields.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ddfb2eb and c2911a9.

📒 Files selected for processing (5)
  • app/src/main/java/com/itsaky/androidide/fragments/CloneRepositoryFragment.kt
  • app/src/main/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModel.kt
  • app/src/test/java/com/itsaky/androidide/viewmodel/CloneRepositoryViewModelTest.kt
  • git-core/src/main/java/com/itsaky/androidide/git/core/models/CloneRepoUiState.kt
  • gradle/libs.versions.toml
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/com/itsaky/androidide/fragments/CloneRepositoryFragment.kt

@dara-abijo-adfa dara-abijo-adfa merged commit e0d74fb into stage Mar 4, 2026
2 checks passed
@dara-abijo-adfa dara-abijo-adfa deleted the ADFA-2882-clone-git-repo branch March 4, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants