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 SourceControlManagement::{
233 DTO::{
234 SourceControlCreateDTO::SourceControlCreateDTO,
235 SourceControlGroupUpdateDTO::SourceControlGroupUpdateDTO,
236 SourceControlInputBoxDTO::SourceControlInputBoxDTO,
237 SourceControlManagementGroupDTO::SourceControlManagementGroupDTO,
238 SourceControlManagementProviderDTO::SourceControlManagementProviderDTO,
239 SourceControlUpdateDTO::SourceControlUpdateDTO,
240 },
241 SourceControlManagementProvider::SourceControlManagementProvider,
242 },
243};
244use async_trait::async_trait;
245use serde_json::{Value, json};
246use tauri::Emitter;
247
248use super::{MountainEnvironment::MountainEnvironment, Utility};
249use crate::dev_log;
250
251#[async_trait]
252impl SourceControlManagementProvider for MountainEnvironment {
253 async fn CreateSourceControl(&self, ProviderDataValue:Value) -> Result<u32, CommonError> {
254 let ProviderData:SourceControlCreateDTO = serde_json::from_value(ProviderDataValue)?;
255
256 let Handle = self.ApplicationState.GetNextSourceControlManagementProviderHandle();
257
258 dev_log!(
259 "extensions",
260 "[SourceControlManagementProvider] Creating new SCM provider with handle {}",
261 Handle
262 );
263
264 let ProviderState = SourceControlManagementProviderDTO {
265 Handle,
266 Label:ProviderData.Label,
267 RootURI:Some(json!({ "external": ProviderData.RootUri.to_string() })),
268 CommitTemplate:None,
269 Count:None,
270 InputBox:None,
271 };
272
273 self.ApplicationState
274 .Feature
275 .Markers
276 .SourceControlManagementProviders
277 .lock()
278 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?
279 .insert(Handle, ProviderState.clone());
280
281 self.ApplicationState
282 .Feature
283 .Markers
284 .SourceControlManagementGroups
285 .lock()
286 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?
287 .insert(Handle, Default::default());
288
289 self.ApplicationHandle
290 .emit("sky://scm/provider/added", ProviderState)
291 .map_err(|Error| {
292 CommonError::UserInterfaceInteraction { Reason:format!("Failed to emit scm event: {}", Error) }
293 })?;
294
295 Ok(Handle)
296 }
297
298 async fn DisposeSourceControl(&self, ProviderHandle:u32) -> Result<(), CommonError> {
299 dev_log!(
300 "extensions",
301 "[SourceControlManagementProvider] Disposing SCM provider with handle {}",
302 ProviderHandle
303 );
304
305 self.ApplicationState
306 .Feature
307 .Markers
308 .SourceControlManagementProviders
309 .lock()
310 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?
311 .remove(&ProviderHandle);
312
313 self.ApplicationState
314 .Feature
315 .Markers
316 .SourceControlManagementGroups
317 .lock()
318 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?
319 .remove(&ProviderHandle);
320
321 self.ApplicationHandle
322 .emit("sky://scm/provider/removed", ProviderHandle)
323 .map_err(|Error| CommonError::UserInterfaceInteraction { Reason:Error.to_string() })?;
324
325 Ok(())
326 }
327
328 async fn UpdateSourceControl(&self, ProviderHandle:u32, UpdateDataValue:Value) -> Result<(), CommonError> {
329 let UpdateData:SourceControlUpdateDTO = serde_json::from_value(UpdateDataValue)?;
330
331 dev_log!(
332 "extensions",
333 "[SourceControlManagementProvider] Updating provider {}",
334 ProviderHandle
335 );
336
337 let mut ProvidersGuard = self
338 .ApplicationState
339 .Feature
340 .Markers
341 .SourceControlManagementProviders
342 .lock()
343 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
344
345 if let Some(Provider) = ProvidersGuard.get_mut(&ProviderHandle) {
346 if let Some(count) = UpdateData.Count {
347 Provider.Count = Some(count);
348 }
349
350 if let Some(value) = UpdateData.InputBoxValue {
351 if let Some(input_box) = &mut Provider.InputBox {
352 input_box.Value = value;
353 }
354 }
355
356 let ProviderClone = Provider.clone();
357
358 // Release lock before emitting
359 drop(ProvidersGuard);
360
361 self.ApplicationHandle
362 .emit(
363 "sky://scm/provider/changed",
364 json!({ "handle": ProviderHandle, "provider": ProviderClone }),
365 )
366 .map_err(|Error| CommonError::UserInterfaceInteraction { Reason:Error.to_string() })?;
367 }
368
369 Ok(())
370 }
371
372 async fn UpdateSourceControlGroup(&self, ProviderHandle:u32, GroupDataValue:Value) -> Result<(), CommonError> {
373 let GroupData:SourceControlGroupUpdateDTO = serde_json::from_value(GroupDataValue)?;
374
375 dev_log!(
376 "extensions",
377 "[SourceControlManagementProvider] Updating group '{}' for provider {}",
378 GroupData.GroupID,
379 ProviderHandle
380 );
381
382 let mut GroupsGuard = self
383 .ApplicationState
384 .Feature
385 .Markers
386 .SourceControlManagementGroups
387 .lock()
388 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
389
390 if let Some(ProviderGroups) = GroupsGuard.get_mut(&ProviderHandle) {
391 let Group = ProviderGroups.entry(GroupData.GroupID.clone()).or_insert_with(|| {
392 SourceControlManagementGroupDTO {
393 ProviderHandle,
394 Identifier:GroupData.GroupID.clone(),
395 Label:GroupData.Label.clone(),
396 }
397 });
398
399 Group.Label = GroupData.Label;
400
401 let GroupClone = Group.clone();
402
403 // Release lock before emitting
404 drop(GroupsGuard);
405
406 self.ApplicationHandle
407 .emit(
408 "sky://scm/group/changed",
409 json!({ "providerHandle": ProviderHandle, "group": GroupClone }),
410 )
411 .map_err(|Error| CommonError::UserInterfaceInteraction { Reason:Error.to_string() })?;
412 } else {
413 dev_log!(
414 "extensions",
415 "warn: [SourceControlManagementProvider] Received group update for unknown provider handle: {}",
416 ProviderHandle
417 );
418 }
419
420 Ok(())
421 }
422
423 async fn RegisterInputBox(&self, ProviderHandle:u32, InputBoxDataValue:Value) -> Result<(), CommonError> {
424 let InputBoxData:SourceControlInputBoxDTO = serde_json::from_value(InputBoxDataValue)?;
425
426 dev_log!(
427 "extensions",
428 "[SourceControlManagementProvider] Registering input box for provider {}",
429 ProviderHandle
430 );
431
432 let mut ProvidersGuard = self
433 .ApplicationState
434 .Feature
435 .Markers
436 .SourceControlManagementProviders
437 .lock()
438 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
439
440 if let Some(Provider) = ProvidersGuard.get_mut(&ProviderHandle) {
441 Provider.InputBox = Some(InputBoxData);
442
443 let ProviderClone = Provider.clone();
444
445 // Release lock before emitting
446 drop(ProvidersGuard);
447
448 self.ApplicationHandle
449 .emit(
450 "sky://scm/provider/changed",
451 json!({ "handle": ProviderHandle, "provider": ProviderClone }),
452 )
453 .map_err(|Error| CommonError::UserInterfaceInteraction { Reason:Error.to_string() })?;
454 }
455
456 Ok(())
457 }
458}