Mountain/Environment/SourceControlManagementProvider.rs
1//! # SourceControlManagementProvider (Environment)
2//!
3//! Implements the `SourceControlManagementProvider` trait for
4//! `MountainEnvironment`, providing Git and other source control management
5//! (SCM) capabilities to the application.
6//!
7//! ## RESPONSIBILITIES
8//!
9//! ### 1. Repository Detection
10//! - Scan workspace folders for Git repositories
11//! - Detect repository root directories
12//! - Track repository state (clean, dirty, branching)
13//! - Monitor repository changes (commit, checkout, merge)
14//!
15//! ### 2. SCM Providers
16//! - Create and manage `SourceControlManagementProvider` instances
17//! - Support multiple SCM systems (Git, Mercurial, etc.)
18//! - Load extension-provided SCM providers
19//! - Route SCM operations to appropriate provider
20//!
21//! ### 3. Change Management
22//! - Track file changes (modified, added, deleted, renamed)
23//! - Provide diff information for changed files
24//! - Support staging and unstaging changes
25//! - Handle merge conflicts
26//!
27//! ### 4. Authentication
28//! - Manage SCM credentials and authentication
29//! - Support SSH keys and HTTPS tokens
30//! - Store credentials securely via `SecretProvider`
31//! - Handle authentication failures and prompts
32//!
33//! ### 5. Operations
34//! - Commit, push, pull, fetch operations
35//! - Branch management (create, delete, rename, checkout)
36//! - Merge and rebase operations
37//! - Remote management (add, remove, rename)
38//!
39//! ## ARCHITECTURAL ROLE
40//!
41//! SourceControlManagementProvider is the **SCM integration layer**:
42//!
43//! ```text
44//! UI (SCM View) ──► SourceControlManagementProvider ──► Git CLI / Libgit2
45//! │
46//! └─► Extension SCM Providers
47//! ```
48//!
49//! ### Position in Mountain
50//! - `Environment` module: SCM capability provider
51//! - Implements
52//! `CommonLibrary::SourceControlManagement::SourceControlManagementProvider`
53//! trait
54//! - Accessible via `Environment.Require<dyn
55//! SourceControlManagementProvider>()`
56//!
57//! ### SCM Provider Hierarchy
58//! - **Built-in Git Provider**: Native Git implementation (preferred)
59//! - **Extension Providers**: Custom SCM support (Mercurial, SVN, etc.)
60//! - **Fallback Providers**: Basic functionality for unknown SCM types
61//!
62//! ### Dependencies
63//! - `SecretProvider`: For storing SCM credentials
64//! - `FileSystemReader` / `FileSystemWriter`: For .git operations
65//! - `Log`: SCM operation logging
66//! - External Git binary or libgit2 library
67//!
68//! ### Dependents
69//! - SCM UI view: Display repository state and changes
70//! - Source control commands: Commit, push, pull, etc.
71//! - `Binary::Main`::`MountainGetWorkbenchConfiguration`: SCM state
72//! - Extension SCM providers: Custom SCM implementations
73//!
74//! ## DATA MODEL
75//!
76//! Stored in `ApplicationState`:
77//! - `SourceControlManagementProviders`: Registered providers by ID
78//! - `SourceControlManagementGroups`: Repository groups (by workspace)
79//! - `SourceControlManagementResources`: Resource state (changed files)
80//!
81//! Key structures:
82//! - `SourceControlManagementProviderDTO`: Provider metadata
83//! - `SourceControlManagementGroupDTO`: Repository group state
84//! - `SourceControlManagementResourceDTO`: Changed file information
85//!
86//! ## REPOSITORY STATES
87//!
88//! - **Clean**: No uncommitted changes
89//! - **Dirty**: Unstaged changes present
90//! - **Staged**: Changes staged for commit
91//! - **Merging**: Merge in progress
92//! - **Rebasing**: Rebase in progress
93//! - **Cherry-picking**: Cherry-pick in progress
94//!
95//! ## ERROR HANDLING
96//!
97//! - Repository not found: `CommonError::SCMNotFound`
98//! - Authentication failure: `CommonError::SCMAuthenticationFailed`
99//! - Operation failure: `CommonError::SCMOperationFailed`
100//! - Merge conflict: `CommonError::SCMConflict`
101//! - Uncommitted changes: `CommonError::SCMUncommittedChanges`
102//!
103//! ## PERFORMANCE
104//!
105//! - Repository scanning should be async and cached
106//! - Use file system watchers to detect changes
107//! - Batch operations when possible (e.g., status of multiple files)
108//! - Consider background indexing for large repositories
109//!
110//! ## VS CODE REFERENCE
111//!
112//! Patterns from VS Code:
113//! - `vs/workbench/services/scm/common/scmService.ts` - SCM service
114//! - `vs/platform/scm/common/scm.ts` - SCM provider interface
115//! - `vs/sourcecontrol/git/common/git.ts` - Git provider implementation
116//!
117//! ## TODO
118//!
119//! - [ ] Implement built-in Git provider using libgit2 or Git CLI
120//! - [ ] Add repository discovery and change detection
121//! - [ ] Support staging, unstaging, and committing changes
122//! - [ ] Implement branch management UI and operations
123//! - [ ] Add remote operations (push, pull, fetch)
124//! - [ ] Handle merge conflicts with UI resolution
125//! - [ ] Support Git LFS and submodules
126//! - [ ] Add SCM authentication and credential management
127//! - [ ] Implement SCM extensions API for custom providers
128//! - [ ] Add SCM history and blame views
129//! - [ ] Support stash and pop operations
130//! - [ ] Implement tag management
131//! - [ ] Add SCM configuration and settings
132//! - [ ] Support detached HEAD and bisect operations
133//! - [ ] Implement SCM telemetry and diagnostics
134//!
135//! ## MODULE CONTENTS
136//!
137//! - [`SourceControlManagementProvider`]: Main struct implementing the trait
138//! - Repository detection and tracking
139//! - Provider registration and routing
140//! - SCM operation implementations
141//! - Authentication and credential management
142
143// `MountainEnvironment`. Responsibilities:
144// - Manage source control providers (e.g., Git, Mercurial, SVN).
145// - Handle SCM provider registration and disposal.
146// - Manage resource groups (e.g., changes, untracked, merge conflicts).
147// - Handle input boxes for user input (e.g., commit messages).
148// - Emit events to the Sky frontend for UI updates.
149// - Provide Git integration patterns for common operations.
150// - Handle conflict detection and resolution.
151// - Support multiple SCM providers simultaneously.
152//
153// TODOs:
154// - Implement complete Git integration (status, commit, push, pull, branch)
155// - Add Git diff display with visual comparison
156// - Implement merge conflict resolution UI
157// - Support Git staging/unstaging of resources
158// - Add Git stash operations
159// - Implement Git branch management (create, delete, checkout)
160// - Support Git remote operations
161// - Add Git history/log viewing
162// - Implement Git blame annotations
163// - Support Git submodules
164// - Implement Git LFS (Large File Storage) support
165// - Add Git tag management
166// - Custom implementation for Mercurial, SVN, and other VCS
167// - Implement SCM provider command registration
168// - Support SCM provider decoration (badges, colors)
169// - Add input box validation and validation messaging
170// - Implement resource state caching for performance
171// - Support SCM provider quick picks and menus
172// - Add keyboard shortcuts for common SCM operations
173// - Implement SCM provider extension points
174// - Support Git rebase and cherry-pick operations
175// - Add Git bisect support
176// - Implement Git commit graph visualization
177// - Support Git hooks integration
178// - Add Git signature verification
179// - Implement Git ignore management
180//
181// Inspired by VSCode's SCM service which:
182// - Provides a flexible abstraction over multiple source control systems
183// - Manages resource state changes through groups
184// - Supports provider-specific operations through commands
185// - Handles UI updates through event emission
186// - Manages input boxes for user interaction
187// - Git integration is the primary implementation with patterns for others
188//! # SourceControlManagementProvider Implementation
189//!
190//! Implements the `SourceControlManagementProvider` trait for the
191//! `MountainEnvironment`.
192//!
193//! ## SCM Provider Architecture
194//!
195//! Each SCM provider maintains:
196//! - **Handle**: Unique identifier for the provider
197//! - **Label**: User-friendly name (e.g., "Git")
198//! - **Root URI**: URI of the repository root
199//! - **Groups**: Resource groups organizing changed resources
200//! - **Input Box**: User input widget for operations (e.g., commit messages)
201//! - **Count**: Badge count for changed items
202//!
203//! ## Resource Groups
204//!
205//! Groups organize resources by their state:
206//! - **Changes**: Modified files ready to commit
207//! - **Untracked**: New files not yet tracked
208//! - **Staged**: Files staged for commit
209//! - **Merge Changes**: Files with merge conflicts
210//! - **Conflict Unresolved**: Unresolved conflict markers
211//
212//! ## SCM Lifecycle
213//!
214//! 1. **Create Provider**: Register a new SCM provider with handle and metadata
215//! 2. **Update Provider**: Update provider state (badge count, input box)
216//! 3. **Update Group**: Add or remove resources from groups
217//! 4. **Register Input Box**: Create input widget for user interaction
218//! 5. **Dispose Provider**: Remove provider and all associated state
219//
220//! ## Git Integration Patterns
221//!
222//! Typical Git provider workflow:
223//! - Detect Git repository via `.git` directory
224//! - Run `git status` to populate resource groups
225//! - Run `git diff` to provide file diffs
226//! - Use input box for commit messages
227//! - Show badge count for changed files
228//! - Provide commands: Stage, Unstage, Commit, Push, Pull, Discard
229
230use CommonLibrary::{
231 Error::CommonError::CommonError,
232 IPC::SkyEvent::SkyEvent,
233 SourceControlManagement::{
234 DTO::{
235 SourceControlCreateDTO::SourceControlCreateDTO,
236 SourceControlGroupUpdateDTO::SourceControlGroupUpdateDTO,
237 SourceControlInputBoxDTO::SourceControlInputBoxDTO,
238 SourceControlManagementGroupDTO::SourceControlManagementGroupDTO,
239 SourceControlManagementProviderDTO::SourceControlManagementProviderDTO,
240 SourceControlUpdateDTO::SourceControlUpdateDTO,
241 },
242 SourceControlManagementProvider::SourceControlManagementProvider,
243 },
244};
245use async_trait::async_trait;
246use serde_json::{Value, json};
247use tauri::Emitter;
248
249use super::{MountainEnvironment::MountainEnvironment, Utility};
250use crate::dev_log;
251
252#[async_trait]
253impl SourceControlManagementProvider for MountainEnvironment {
254 async fn CreateSourceControl(&self, ProviderDataValue:Value) -> Result<u32, CommonError> {
255 let ProviderData:SourceControlCreateDTO = serde_json::from_value(ProviderDataValue)?;
256
257 // Honor caller-supplied handle when present so the marker maps
258 // (`SourceControlManagementProviders` / `SourceControlManagementGroups`)
259 // key under the SAME identifier Cocoon's `ScmNamespace.ts` uses
260 // for subsequent `register_scm_resource_group` and `update_scm_group`
261 // notifications. Without this, `UpdateSourceControlGroup` looks up
262 // Cocoon's handle in a map keyed by a Mountain-allocated handle,
263 // the entry isn't there, and every group update warns
264 // "Received group update for unknown provider handle: <H>" while
265 // the SCM viewlet stays empty.
266 let Handle = ProviderData
267 .Handle
268 .unwrap_or_else(|| self.ApplicationState.GetNextSourceControlManagementProviderHandle());
269
270 dev_log!(
271 "extensions",
272 "[SourceControlManagementProvider] Creating new SCM provider with handle {}",
273 Handle
274 );
275
276 let ProviderState = SourceControlManagementProviderDTO {
277 Handle,
278 Identifier:ProviderData.ID.clone(),
279 Label:ProviderData.Label,
280 RootURI:Some(json!({ "external": ProviderData.RootUri.to_string() })),
281 CommitTemplate:None,
282 Count:None,
283 InputBox:None,
284 };
285
286 self.ApplicationState
287 .Feature
288 .Markers
289 .SourceControlManagementProviders
290 .lock()
291 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?
292 .insert(Handle, ProviderState.clone());
293
294 self.ApplicationState
295 .Feature
296 .Markers
297 .SourceControlManagementGroups
298 .lock()
299 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?
300 .insert(Handle, Default::default());
301
302 self.ApplicationHandle
303 .emit(SkyEvent::SCMProviderAdded.AsStr(), ProviderState)
304 .map_err(|Error| {
305 CommonError::UserInterfaceInteraction { Reason:format!("Failed to emit scm event: {}", Error) }
306 })?;
307
308 Ok(Handle)
309 }
310
311 async fn DisposeSourceControl(&self, ProviderHandle:u32) -> Result<(), CommonError> {
312 dev_log!(
313 "extensions",
314 "[SourceControlManagementProvider] Disposing SCM provider with handle {}",
315 ProviderHandle
316 );
317
318 self.ApplicationState
319 .Feature
320 .Markers
321 .SourceControlManagementProviders
322 .lock()
323 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?
324 .remove(&ProviderHandle);
325
326 self.ApplicationState
327 .Feature
328 .Markers
329 .SourceControlManagementGroups
330 .lock()
331 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?
332 .remove(&ProviderHandle);
333
334 self.ApplicationHandle
335 .emit(SkyEvent::SCMProviderRemoved.AsStr(), ProviderHandle)
336 .map_err(|Error| CommonError::UserInterfaceInteraction { Reason:Error.to_string() })?;
337
338 Ok(())
339 }
340
341 async fn UpdateSourceControl(&self, ProviderHandle:u32, UpdateDataValue:Value) -> Result<(), CommonError> {
342 let UpdateData:SourceControlUpdateDTO = serde_json::from_value(UpdateDataValue)?;
343
344 dev_log!(
345 "extensions",
346 "[SourceControlManagementProvider] Updating provider {}",
347 ProviderHandle
348 );
349
350 let mut ProvidersGuard = self
351 .ApplicationState
352 .Feature
353 .Markers
354 .SourceControlManagementProviders
355 .lock()
356 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?;
357
358 if let Some(Provider) = ProvidersGuard.get_mut(&ProviderHandle) {
359 if let Some(count) = UpdateData.Count {
360 Provider.Count = Some(count);
361 }
362
363 if let Some(value) = UpdateData.InputBoxValue {
364 if let Some(input_box) = &mut Provider.InputBox {
365 input_box.Value = value;
366 }
367 }
368
369 let ProviderClone = Provider.clone();
370
371 // Release lock before emitting
372 drop(ProvidersGuard);
373
374 self.ApplicationHandle
375 .emit(
376 SkyEvent::SCMProviderChanged.AsStr(),
377 json!({ "handle": ProviderHandle, "provider": ProviderClone }),
378 )
379 .map_err(|Error| CommonError::UserInterfaceInteraction { Reason:Error.to_string() })?;
380 }
381
382 Ok(())
383 }
384
385 async fn UpdateSourceControlGroup(&self, ProviderHandle:u32, GroupDataValue:Value) -> Result<(), CommonError> {
386 let GroupData:SourceControlGroupUpdateDTO = serde_json::from_value(GroupDataValue)?;
387
388 dev_log!(
389 "extensions",
390 "[SourceControlManagementProvider] Updating group '{}' for provider {}",
391 GroupData.GroupID,
392 ProviderHandle
393 );
394
395 let mut GroupsGuard = self
396 .ApplicationState
397 .Feature
398 .Markers
399 .SourceControlManagementGroups
400 .lock()
401 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?;
402
403 if let Some(ProviderGroups) = GroupsGuard.get_mut(&ProviderHandle) {
404 let Group = ProviderGroups.entry(GroupData.GroupID.clone()).or_insert_with(|| {
405 SourceControlManagementGroupDTO {
406 ProviderHandle,
407 Identifier:GroupData.GroupID.clone(),
408 Label:GroupData.Label.clone(),
409 }
410 });
411
412 Group.Label = GroupData.Label;
413
414 let GroupClone = Group.clone();
415
416 // Release lock before emitting
417 drop(GroupsGuard);
418
419 self.ApplicationHandle
420 .emit(
421 SkyEvent::SCMGroupChanged.AsStr(),
422 json!({ "providerHandle": ProviderHandle, "group": GroupClone }),
423 )
424 .map_err(|Error| CommonError::UserInterfaceInteraction { Reason:Error.to_string() })?;
425 } else {
426 dev_log!(
427 "extensions",
428 "warn: [SourceControlManagementProvider] Received group update for unknown provider handle: {}",
429 ProviderHandle
430 );
431 }
432
433 Ok(())
434 }
435
436 async fn RegisterInputBox(&self, ProviderHandle:u32, InputBoxDataValue:Value) -> Result<(), CommonError> {
437 let InputBoxData:SourceControlInputBoxDTO = serde_json::from_value(InputBoxDataValue)?;
438
439 dev_log!(
440 "extensions",
441 "[SourceControlManagementProvider] Registering input box for provider {}",
442 ProviderHandle
443 );
444
445 let mut ProvidersGuard = self
446 .ApplicationState
447 .Feature
448 .Markers
449 .SourceControlManagementProviders
450 .lock()
451 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?;
452
453 if let Some(Provider) = ProvidersGuard.get_mut(&ProviderHandle) {
454 Provider.InputBox = Some(InputBoxData);
455
456 let ProviderClone = Provider.clone();
457
458 // Release lock before emitting
459 drop(ProvidersGuard);
460
461 self.ApplicationHandle
462 .emit(
463 SkyEvent::SCMProviderChanged.AsStr(),
464 json!({ "handle": ProviderHandle, "provider": ProviderClone }),
465 )
466 .map_err(|Error| CommonError::UserInterfaceInteraction { Reason:Error.to_string() })?;
467 }
468
469 Ok(())
470 }
471}