123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- // Copyright (c) 2025 TerraByte Inc.
- //
- // This script acts as the Controller for the ArbitratorWindow. It manages all
- // state and business logic, separating it from the UI rendering code in the window.
- using System;
- using System.Linq;
- using UnityEditor;
- using Terra.Arbitrator.Settings;
- using Terra.Arbitrator.Services;
- using Terra.Arbitrator.Promises;
- using System.Collections.Generic;
- namespace Terra.Arbitrator.GUI
- {
- public class ArbitratorController
- {
- private List<GitChange> _changes = new();
- public IReadOnlyList<GitChange> Changes => _changes;
- public string InfoMessage { get; private set; }
- public string ErrorMessage { get; private set; }
- public bool IsLoading { get; private set; }
- public string LoadingMessage { get; private set; } = "";
- private readonly Action _requestRepaint;
- private readonly Func<string, string, bool> _displayDialog;
- /// <summary>
- /// Initializes the controller.
- /// </summary>
- /// <param name="requestRepaint">A callback to the window's Repaint() method.</param>
- /// <param name="displayDialog">A callback to EditorUtility.DisplayDialog for user confirmations.</param>
- public ArbitratorController(Action requestRepaint, Func<string, string, bool> displayDialog)
- {
- _requestRepaint = requestRepaint;
- _displayDialog = displayDialog;
- }
-
- public void OnEnable()
- {
- if (SessionState.GetBool(BetterGitStatePersistence.ResetQueueKey, false))
- {
- SessionState.EraseString(BetterGitStatePersistence.ResetQueueKey);
- InfoMessage = "Multi-file reset complete. Pulling again to confirm...";
- Pull();
- }
- else
- {
- Refresh();
- }
- }
- public void Refresh()
- {
- StartOperation("Refreshing status...");
-
- UnstageStep()
- .Then(CompareStep)
- .Then(changes => _changes = changes)
- .Catch(HandleOperationError)
- .Finally(FinishOperation);
- }
-
- public void Pull()
- {
- if (IsLoading) return;
- StartOperation("Analyzing for conflicts...");
- GitService.AnalyzePullConflicts()
- .Then(analysisResult =>
- {
- if (analysisResult.HasConflicts)
- {
- FinishOperation();
- ConflictResolutionWindow.ShowWindow(analysisResult.ConflictingFiles, filesToActOn =>
- {
- if (filesToActOn == null) return;
- if (filesToActOn.Any()) ResetMultipleFiles(filesToActOn);
- else ForcePull();
- });
- }
- else
- {
- StartOperation("No conflicts found. Pulling changes...");
- GitService.PerformSafePull()
- .Then(successMessage =>
- {
- InfoMessage = successMessage;
- // Refresh will handle its own start/finish states
- Refresh();
- })
- .Catch(ex => {
- // If the pull fails, we need to handle the error and finish
- HandleOperationError(ex);
- FinishOperation();
- });
- }
- })
- .Catch(ex => {
- HandleOperationError(ex);
- FinishOperation();
- });
- }
- public void CommitAndPush(string commitMessage)
- {
- var selectedFiles = _changes.Where(c => c.IsSelectedForCommit).ToList();
-
- var username = BetterGitSettings.Username;
- var email = BetterGitSettings.Email;
-
- StartOperation("Staging, committing, and pushing files...");
- GitService.CommitAndPush(selectedFiles, commitMessage, username, email)
- .Then(successMessage => {
- InfoMessage = successMessage;
- Refresh();
- })
- .Catch(ex => {
- HandleOperationError(ex);
- FinishOperation();
- });
- }
- public void ResetFile(GitChange change)
- {
- if (IsLoading) return;
- var userConfirmed = _displayDialog("Confirm Reset", $"Are you sure you want to revert all local changes to '{change.FilePath}'? This action cannot be undone.");
- if (!userConfirmed) return;
-
- StartOperation($"Resetting {change.FilePath}...");
-
- GitService.ResetFileChanges(change)
- .Then(successMessage => {
- InfoMessage = successMessage;
- Refresh();
- })
- .Catch(ex => {
- HandleOperationError(ex);
- FinishOperation();
- });
- }
-
- public void DiffFile(GitChange change)
- {
- if (IsLoading) return;
- StartOperation($"Launching diff for {change.FilePath}...");
-
- GitService.LaunchExternalDiff(change)
- .Catch(HandleOperationError)
- .Finally(Refresh);
- }
-
- public void ResolveConflict(GitChange change)
- {
- if (IsLoading) return;
- StartOperation($"Opening merge tool for {change.FilePath}...");
-
- GitService.LaunchMergeTool(change)
- .Then(successMessage => { InfoMessage = successMessage; })
- .Catch(HandleOperationError)
- .Finally(Refresh);
- }
- public void ResetAll()
- {
- var userConfirmed = _displayDialog(
- "Confirm Reset All",
- "Are you sure you want to discard ALL local changes (modified, added, and untracked files)?\n\nThis action cannot be undone.");
- if (!userConfirmed) return;
- StartOperation("Discarding all local changes...");
- GitService.ResetAllChanges()
- .Then(successMessage =>
- {
- InfoMessage = successMessage;
- })
- .Catch(HandleOperationError)
- .Finally(Refresh);
- }
- public void SetAllSelection(bool selected)
- {
- if (_changes == null) return;
- foreach (var change in _changes)
- {
- if (change.Status != LibGit2Sharp.ChangeKind.Conflicted)
- {
- change.IsSelectedForCommit = selected;
- }
- }
- }
- // --- Private Methods ---
-
- private static IPromise<bool> UnstageStep()
- {
- return GitService.UnstageAllFilesIfSafe();
- }
- private IPromise<List<GitChange>> CompareStep(bool wasUnstaged)
- {
- if (wasUnstaged)
- {
- InfoMessage = "Found and unstaged files for review.";
- }
- return GitService.CompareLocalToRemote();
- }
-
- // --- Shared Helper Methods ---
- private void StartOperation(string loadingMessage)
- {
- IsLoading = true;
- LoadingMessage = loadingMessage;
- ClearMessages();
- _changes = null;
- _requestRepaint?.Invoke();
- }
- private void HandleOperationError(Exception ex)
- {
- ErrorMessage = $"Operation Failed: {ex.Message}";
- }
-
- private void FinishOperation()
- {
- IsLoading = false;
- _requestRepaint?.Invoke();
- }
-
- private void ClearMessages()
- {
- ErrorMessage = null;
- InfoMessage = null;
- }
- private void ResetMultipleFiles(List<string> filePaths)
- {
- InfoMessage = $"Starting reset for {filePaths.Count} file(s)... This may trigger script compilation.";
- _requestRepaint?.Invoke();
- SessionState.SetString("BetterGit.ResetQueue", string.Join(";", filePaths));
- EditorApplication.delayCall += BetterGitStatePersistence.ContinueInterruptedReset;
- }
-
- private void ForcePull()
- {
- StartOperation("Attempting to pull and create conflicts...");
-
- EditorApplication.LockReloadAssemblies();
- GitService.ForcePull()
- .Then(_ =>
- {
- InfoMessage = "Pull resulted in conflicts. Please resolve them below.";
- })
- .Catch(HandleOperationError)
- .Finally(() =>
- {
- EditorApplication.UnlockReloadAssemblies();
- AssetDatabase.Refresh();
- Refresh();
- });
- }
- }
- }
|