ArbitratorWindow.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. // Copyright (c) 2025 TerraByte Inc.
  2. //
  3. // This script creates a custom Unity Editor window called "Arbitrator" to compare
  4. // local Git changes with the tracked remote branch using the LibGit2Sharp library.
  5. //
  6. // HOW TO USE:
  7. // 1. Ensure you have manually installed the LibGit2Sharp v0.27.0 package.
  8. // 2. Create an "Editor" folder in your Assets directory if you don't have one.
  9. // 3. Save this script as "Arbitrator.cs" inside the "Editor" folder.
  10. // 4. In Unity, open the window from the top menu: Terra > Arbitrator.
  11. // 5. Click the "Compare with Cloud" button. Results will appear in the console.
  12. using System.Linq;
  13. using UnityEngine;
  14. using UnityEditor;
  15. using LibGit2Sharp;
  16. using System.Collections.Generic;
  17. namespace Terra.Arbitrator
  18. {
  19. public class ArbitratorWindow : EditorWindow
  20. {
  21. private List<GitChange> _changes;
  22. private string _commitMessage = "";
  23. private Vector2 _scrollPosition;
  24. private string _infoMessage;
  25. private string _errorMessage;
  26. private bool _isLoading;
  27. private string _loadingMessage = "";
  28. [MenuItem("Terra/Changes")]
  29. public static void ShowWindow()
  30. {
  31. var window = GetWindow<ArbitratorWindow>();
  32. window.titleContent = new GUIContent("Changes", EditorGUIUtility.IconContent("d_UnityEditor.VersionControl").image);
  33. }
  34. /// <summary>
  35. /// Called when the window is enabled. This triggers an automatic refresh
  36. /// when the window is opened or after scripts are recompiled.
  37. /// </summary>
  38. private void OnEnable()
  39. {
  40. HandleCompare();
  41. }
  42. private void OnGUI()
  43. {
  44. // --- Top Toolbar ---
  45. DrawToolbar();
  46. EditorGUILayout.Space();
  47. // --- Message Display Area ---
  48. if (!string.IsNullOrEmpty(_errorMessage))
  49. {
  50. EditorGUILayout.HelpBox(_errorMessage, MessageType.Error);
  51. }
  52. else if (!string.IsNullOrEmpty(_infoMessage) && !_isLoading)
  53. {
  54. // Only show an info message if not loading, to prevent flicker
  55. EditorGUILayout.HelpBox(_infoMessage, MessageType.Info);
  56. }
  57. // --- Main Content ---
  58. if (_isLoading)
  59. {
  60. // You can add a more prominent loading indicator here if you wish
  61. }
  62. else if (_changes is { Count: > 0 })
  63. {
  64. DrawChangesList();
  65. DrawCommitSection();
  66. }
  67. }
  68. private void ClearMessages()
  69. {
  70. _errorMessage = null;
  71. _infoMessage = null;
  72. }
  73. /// <summary>
  74. /// Draws the top menu bar for actions like refreshing.
  75. /// </summary>
  76. private void DrawToolbar()
  77. {
  78. EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
  79. // The refresh button is now on the toolbar
  80. if (GUILayout.Button(new GUIContent("Refresh", EditorGUIUtility.IconContent("Refresh").image, "Fetches the latest status from the remote."), EditorStyles.toolbarButton, GUILayout.Width(80)))
  81. {
  82. HandleCompare();
  83. }
  84. // This pushes everything that comes after it to the right.
  85. GUILayout.FlexibleSpace();
  86. // The loading message will appear on the right side of the toolbar.
  87. if (_isLoading)
  88. {
  89. GUILayout.Label(_loadingMessage);
  90. }
  91. // Future: Add a dropdown menu for filters or settings here.
  92. // If (GUILayout.Button("Filters", EditorStyles.toolbarDropDown)) { ... }
  93. EditorGUILayout.EndHorizontal();
  94. }
  95. private void HandleCompare()
  96. {
  97. _isLoading = true;
  98. _loadingMessage = "Comparing with remote repository...";
  99. ClearMessages();
  100. _changes = null;
  101. GitService.CompareLocalToRemote()
  102. .Then(result => {
  103. _changes = result;
  104. if (_changes.Count == 0)
  105. {
  106. _infoMessage = "You are up-to-date! No local changes detected.";
  107. }
  108. })
  109. .Catch(ex => {
  110. _errorMessage = $"Comparison Failed: {ex.Message}";
  111. })
  112. .Finally(() => {
  113. _isLoading = false;
  114. Repaint(); // Redraw the window with the new state
  115. });
  116. }
  117. private void HandleCommitAndPush()
  118. {
  119. _isLoading = true;
  120. _loadingMessage = "Staging, committing, and pushing files...";
  121. ClearMessages();
  122. var selectedFiles = _changes.Where(c => c.IsSelectedForCommit).ToList();
  123. GitService.CommitAndPush(selectedFiles, _commitMessage)
  124. .Then(successMessage => {
  125. _infoMessage = successMessage;
  126. _commitMessage = ""; // Clear message on success
  127. _changes = null; // Clear the list, forcing a refresh
  128. HandleCompare(); // Automatically refresh to confirm
  129. })
  130. .Catch(ex => {
  131. _errorMessage = $"Push Failed: {ex.Message}";
  132. })
  133. .Finally(() => {
  134. _isLoading = false;
  135. // Repaint is handled by the chained HandleCompare call on success
  136. if (!string.IsNullOrEmpty(_errorMessage)) Repaint();
  137. });
  138. }
  139. private void HandleResetFile(GitChange change)
  140. {
  141. if (_isLoading) return;
  142. _isLoading = true;
  143. _loadingMessage = $"Generating diff for {change.FilePath}...";
  144. ClearMessages();
  145. Repaint();
  146. // Step 1: Get the diff content for the file.
  147. GitService.GetFileDiff(change)
  148. .Then(diffContent =>
  149. {
  150. // This callback runs on the main thread when the diff is ready.
  151. // Step 2: Show the modal diff window.
  152. DiffWindow.ShowWindow(change.FilePath, diffContent, wasConfirmed =>
  153. {
  154. // This callback runs after the diff window is closed.
  155. if (!wasConfirmed)
  156. {
  157. _isLoading = false; // User cancelled.
  158. Repaint();
  159. return;
  160. }
  161. // Step 3: User confirmed. Proceed to reset the file.
  162. _loadingMessage = $"Resetting {change.FilePath}...";
  163. Repaint();
  164. GitService.ResetFileChanges(change)
  165. .Then(successMessage => {
  166. _infoMessage = successMessage;
  167. HandleCompare(); // Refresh the main list.
  168. })
  169. .Catch(ex => {
  170. _errorMessage = $"Reset Failed: {ex.Message}";
  171. _isLoading = false;
  172. Repaint();
  173. });
  174. });
  175. })
  176. .Catch(ex =>
  177. {
  178. // This runs if getting the diff itself failed.
  179. _errorMessage = $"Could not generate diff: {ex.Message}";
  180. _isLoading = false;
  181. Repaint();
  182. })
  183. .Finally(HandleCompare);
  184. }
  185. /// <summary>
  186. /// Draws the multi-column list of changed files.
  187. /// </summary>
  188. private void DrawChangesList()
  189. {
  190. // --- Draw Header ---
  191. EditorGUILayout.BeginHorizontal(EditorStyles.helpBox);
  192. EditorGUILayout.LabelField("Commit", GUILayout.Width(45));
  193. EditorGUILayout.LabelField("Status", GUILayout.Width(50));
  194. EditorGUILayout.LabelField("File Path");
  195. EditorGUILayout.LabelField("Actions", GUILayout.Width(55));
  196. EditorGUILayout.EndHorizontal();
  197. // --- Draw Scrollable List ---
  198. _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition, GUILayout.ExpandHeight(true));
  199. foreach (var change in _changes)
  200. {
  201. EditorGUILayout.BeginHorizontal();
  202. // Column 1: Toggle Box
  203. change.IsSelectedForCommit = EditorGUILayout.Toggle(change.IsSelectedForCommit, GUILayout.Width(45));
  204. // Column 2: Status
  205. string status;
  206. Color statusColor;
  207. switch (change.Status)
  208. {
  209. case ChangeKind.Added: status = "[+]"; statusColor = Color.green; break;
  210. case ChangeKind.Deleted: status = "[-]"; statusColor = Color.red; break;
  211. case ChangeKind.Modified: status = "[M]"; statusColor = new Color(1.0f, 0.6f, 0.0f); break;
  212. case ChangeKind.Unmodified:
  213. case ChangeKind.Renamed:
  214. case ChangeKind.Copied:
  215. case ChangeKind.Ignored:
  216. case ChangeKind.Untracked:
  217. case ChangeKind.TypeChanged:
  218. case ChangeKind.Unreadable:
  219. case ChangeKind.Conflicted:
  220. status = "[C]"; statusColor = new Color(0.5f, 0.5f, 0.5f, 0.3f); break;
  221. default: status = "[?]"; statusColor = Color.white; break;
  222. }
  223. var originalColor = GUI.color;
  224. GUI.color = statusColor;
  225. EditorGUILayout.LabelField(new GUIContent(status, change.Status.ToString()), GUILayout.Width(50));
  226. GUI.color = originalColor;
  227. // Column 3: File Path
  228. EditorGUILayout.LabelField(new GUIContent(change.FilePath, change.FilePath));
  229. // Column 4: Reset Button
  230. EditorGUI.BeginDisabledGroup(_isLoading);
  231. if (GUILayout.Button(new GUIContent("Reset", "Revert changes for this file"), GUILayout.Width(55)))
  232. {
  233. // Defers the action to avoid GUI layout errors
  234. EditorApplication.delayCall += () => HandleResetFile(change);
  235. }
  236. EditorGUI.EndDisabledGroup();
  237. EditorGUILayout.EndHorizontal();
  238. }
  239. EditorGUILayout.EndScrollView();
  240. }
  241. private void DrawCommitSection()
  242. {
  243. EditorGUILayout.Space(10);
  244. EditorGUILayout.LabelField("Commit & Push", EditorStyles.boldLabel);
  245. _commitMessage = EditorGUILayout.TextArea(_commitMessage, GUILayout.Height(60), GUILayout.ExpandWidth(true));
  246. var isPushDisabled = string.IsNullOrWhiteSpace(_commitMessage) || !_changes.Any(c => c.IsSelectedForCommit);
  247. EditorGUI.BeginDisabledGroup(isPushDisabled);
  248. if (GUILayout.Button("Commit & Push Selected Files", GUILayout.Height(40)))
  249. {
  250. HandleCommitAndPush();
  251. }
  252. EditorGUI.EndDisabledGroup();
  253. if (isPushDisabled)
  254. {
  255. EditorGUILayout.HelpBox("Please enter a commit message and select at least one file.", MessageType.Warning);
  256. }
  257. }
  258. }
  259. }