using UnityEngine; using UnityEditor; using System; using System.Collections.Generic; using System.Linq; namespace UnityVersionControl { /// /// Branch and Stash Management System /// Provides GitHub Desktop-style branch switching and stash management /// public class BranchAndStashManager : EditorWindow { #region Fields private Vector2 scrollPosition; private int selectedTab = 0; private readonly string[] tabNames = { "Branches", "Stashes" }; // Branch data private readonly List branches = new List(); private readonly List stashes = new List(); private string currentBranch = "main"; private string newBranchName = ""; private bool showCreateBranchDialog = false; private bool showStashDialog = false; private string stashMessage = ""; // UI state private string searchFilter = ""; private bool showMergedBranches = false; private BranchInfo branchToDelete = null; private StashInfo stashToDelete = null; // Visual constants private static readonly Color backgroundColor = new Color(0.22f, 0.22f, 0.22f); private static readonly Color cardColor = new Color(0.28f, 0.28f, 0.28f); private static readonly Color accentColor = new Color(0.3f, 0.7f, 1f); private static readonly Color successColor = new Color(0.3f, 0.8f, 0.3f); private static readonly Color warningColor = new Color(1f, 0.8f, 0.2f); private static readonly Color errorColor = new Color(1f, 0.4f, 0.4f); private static readonly Color currentBranchColor = new Color(0.2f, 0.8f, 0.2f); private static readonly Color remoteBranchColor = new Color(0.6f, 0.6f, 1f); // Caches private readonly Dictionary textureCache = new Dictionary(); #endregion #region Unity Lifecycle [MenuItem("Window/Version Control/Branch & Stash Manager", false, 2)] public static void ShowWindow() { var window = GetWindow(); window.titleContent = new GUIContent("Branches & Stashes"); window.minSize = new Vector2(400, 500); window.Initialize(); } private void OnEnable() { Initialize(); } private void OnDisable() { CleanupTextures(); } private void OnGUI() { DrawBackground(); DrawHeader(); DrawTabs(); EditorGUILayout.Space(8); switch (selectedTab) { case 0: DrawBranchesTab(); break; case 1: DrawStashesTab(); break; } DrawDialogs(); } #endregion #region Initialization private void Initialize() { LoadMockBranches(); LoadMockStashes(); } private void LoadMockBranches() { branches.Clear(); // Current branch branches.Add(new BranchInfo { name = "main", isRemote = false, isCurrent = true, lastCommitMessage = "Initial commit", lastCommitAuthor = "Developer", lastCommitDate = DateTime.Now.AddDays(-1), commitHash = "a1b2c3d", aheadCount = 0, behindCount = 0 }); // Local branches branches.Add(new BranchInfo { name = "feature/player-movement", isRemote = false, isCurrent = false, lastCommitMessage = "Add player jump mechanics", lastCommitAuthor = "John Doe", lastCommitDate = DateTime.Now.AddHours(-3), commitHash = "b2c3d4e", aheadCount = 2, behindCount = 0 }); branches.Add(new BranchInfo { name = "bugfix/ui-scaling", isRemote = false, isCurrent = false, lastCommitMessage = "Fix UI scaling on different resolutions", lastCommitAuthor = "Jane Smith", lastCommitDate = DateTime.Now.AddHours(-5), commitHash = "c3d4e5f", aheadCount = 1, behindCount = 1 }); // Remote branches branches.Add(new BranchInfo { name = "origin/feature/audio-system", isRemote = true, isCurrent = false, lastCommitMessage = "Implement 3D spatial audio", lastCommitAuthor = "Bob Johnson", lastCommitDate = DateTime.Now.AddHours(-8), commitHash = "d4e5f6g", aheadCount = 0, behindCount = 3 }); branches.Add(new BranchInfo { name = "origin/feature/multiplayer", isRemote = true, isCurrent = false, lastCommitMessage = "Add network synchronization", lastCommitAuthor = "Alice Brown", lastCommitDate = DateTime.Now.AddDays(-2), commitHash = "e5f6g7h", aheadCount = 0, behindCount = 5 }); } private void LoadMockStashes() { stashes.Clear(); stashes.Add(new StashInfo { id = "stash@{0}", message = "WIP: Player controller improvements", branchName = "feature/player-movement", author = "Developer", date = DateTime.Now.AddMinutes(-30), changedFiles = new List { "Assets/Scripts/PlayerController.cs", "Assets/Scripts/InputManager.cs", "Assets/Prefabs/Player.prefab" } }); stashes.Add(new StashInfo { id = "stash@{1}", message = "Experimental UI changes", branchName = "main", author = "Developer", date = DateTime.Now.AddHours(-2), changedFiles = new List { "Assets/UI/MainMenu.prefab", "Assets/Materials/UI_Button.mat" } }); stashes.Add(new StashInfo { id = "stash@{2}", message = "Audio volume adjustments", branchName = "feature/audio-system", author = "Developer", date = DateTime.Now.AddDays(-1), changedFiles = new List { "Assets/Audio/AudioMixer.mixer", "Assets/Scripts/AudioManager.cs" } }); } private void CleanupTextures() { foreach (var texture in textureCache.Values) { if (texture != null) DestroyImmediate(texture); } textureCache.Clear(); } #endregion #region GUI Drawing private void DrawBackground() { var rect = new Rect(0, 0, position.width, position.height); EditorGUI.DrawRect(rect, backgroundColor); } private void DrawHeader() { DrawCard(() => { using (new EditorGUILayout.HorizontalScope()) { // Current branch indicator var currentBranchRect = GUILayoutUtility.GetRect(20, 20); var branchTexture = CreateRoundedTexture(currentBranchColor, 20, 10); GUI.DrawTexture(currentBranchRect, branchTexture); var branchIconStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel); branchIconStyle.normal.textColor = Color.white; branchIconStyle.fontSize = 10; branchIconStyle.fontStyle = FontStyle.Bold; GUI.Label(currentBranchRect, "⎇", branchIconStyle); GUILayout.Space(8); using (new EditorGUILayout.VerticalScope()) { var titleStyle = new GUIStyle(EditorStyles.boldLabel); titleStyle.fontSize = 14; titleStyle.normal.textColor = Color.white; EditorGUILayout.LabelField($"Current Branch: {currentBranch}", titleStyle); var currentBranchInfo = branches.FirstOrDefault(b => b.isCurrent); if (currentBranchInfo != null) { var subtitleStyle = new GUIStyle(EditorStyles.label); subtitleStyle.normal.textColor = new Color(0.8f, 0.8f, 0.8f); subtitleStyle.fontSize = 11; var statusText = ""; if (currentBranchInfo.aheadCount > 0) statusText += $"↑{currentBranchInfo.aheadCount} "; if (currentBranchInfo.behindCount > 0) statusText += $"↓{currentBranchInfo.behindCount} "; if (string.IsNullOrEmpty(statusText)) statusText = "Up to date"; EditorGUILayout.LabelField(statusText.Trim(), subtitleStyle); } } GUILayout.FlexibleSpace(); // Quick actions var createBranchStyle = new GUIStyle(GUI.skin.button); createBranchStyle.normal.textColor = successColor; createBranchStyle.fixedHeight = 28; if (GUILayout.Button("+ NEW BRANCH", createBranchStyle, GUILayout.Width(100))) ShowCreateBranchDialog(); GUILayout.Space(4); var stashStyle = new GUIStyle(GUI.skin.button); stashStyle.normal.textColor = warningColor; stashStyle.fixedHeight = 28; if (GUILayout.Button("📦 STASH", stashStyle, GUILayout.Width(80))) ShowStashDialog(); } }, 12); } private void DrawTabs() { EditorGUILayout.Space(8); using (new EditorGUILayout.HorizontalScope()) { for (int i = 0; i < tabNames.Length; i++) { var isSelected = selectedTab == i; var buttonStyle = new GUIStyle(GUI.skin.button); if (isSelected) { buttonStyle.normal.background = Texture2D.whiteTexture; buttonStyle.normal.textColor = backgroundColor; buttonStyle.fontStyle = FontStyle.Bold; } else { buttonStyle.normal.textColor = new Color(0.8f, 0.8f, 0.8f); } buttonStyle.fixedHeight = 32; var tabLabel = tabNames[i]; if (i == 0) tabLabel += $" ({branches.Count})"; if (i == 1) tabLabel += $" ({stashes.Count})"; if (GUILayout.Button(tabLabel, buttonStyle)) selectedTab = i; } } } private void DrawBranchesTab() { EditorGUILayout.Space(8); DrawBranchFilters(); DrawBranchList(); } private void DrawBranchFilters() { DrawCard(() => { using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField("Search:", GUILayout.Width(50)); searchFilter = EditorGUILayout.TextField(searchFilter); GUILayout.Space(8); var oldShowMerged = showMergedBranches; showMergedBranches = EditorGUILayout.Toggle("Show merged", showMergedBranches); if (oldShowMerged != showMergedBranches) Repaint(); } }, 8); } private void DrawBranchList() { EditorGUILayout.Space(8); var filteredBranches = branches.Where(b => string.IsNullOrEmpty(searchFilter) || b.name.ToLower().Contains(searchFilter.ToLower()) ).ToList(); using (var scrollView = new EditorGUILayout.ScrollViewScope(scrollPosition)) { scrollPosition = scrollView.scrollPosition; // Group branches by type var localBranches = filteredBranches.Where(b => !b.isRemote).ToList(); var remoteBranches = filteredBranches.Where(b => b.isRemote).ToList(); if (localBranches.Count > 0) { DrawBranchSection("Local Branches", localBranches); } if (remoteBranches.Count > 0) { EditorGUILayout.Space(12); DrawBranchSection("Remote Branches", remoteBranches); } } } private void DrawBranchSection(string title, List sectionBranches) { DrawCard(() => { var sectionStyle = new GUIStyle(EditorStyles.boldLabel); sectionStyle.fontSize = 12; sectionStyle.normal.textColor = new Color(0.8f, 0.8f, 0.8f); EditorGUILayout.LabelField(title, sectionStyle); EditorGUILayout.Space(8); foreach (var branch in sectionBranches) { DrawBranchItem(branch); if (branch != sectionBranches.Last()) DrawSeparator(1f, 4f); } }, 12); } private void DrawBranchItem(BranchInfo branch) { using (new EditorGUILayout.HorizontalScope()) { // Branch icon and name var iconSize = 24; var iconRect = GUILayoutUtility.GetRect(iconSize, iconSize); Color iconColor; string iconText; if (branch.isCurrent) { iconColor = currentBranchColor; iconText = "●"; } else if (branch.isRemote) { iconColor = remoteBranchColor; iconText = "⊗"; } else { iconColor = accentColor; iconText = "⎇"; } var iconTexture = CreateRoundedTexture(iconColor, iconSize, iconSize / 2); GUI.DrawTexture(iconRect, iconTexture); var iconStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel); iconStyle.normal.textColor = Color.white; iconStyle.fontSize = 10; iconStyle.fontStyle = FontStyle.Bold; GUI.Label(iconRect, iconText, iconStyle); GUILayout.Space(8); // Branch info using (new EditorGUILayout.VerticalScope()) { using (new EditorGUILayout.HorizontalScope()) { var nameStyle = new GUIStyle(EditorStyles.boldLabel); nameStyle.fontSize = 13; nameStyle.normal.textColor = branch.isCurrent ? currentBranchColor : Color.white; EditorGUILayout.LabelField(branch.name, nameStyle); GUILayout.FlexibleSpace(); // Ahead/behind indicators if (branch.aheadCount > 0) { DrawCommitCountBadge($"↑{branch.aheadCount}", successColor); GUILayout.Space(4); } if (branch.behindCount > 0) { DrawCommitCountBadge($"↓{branch.behindCount}", warningColor); GUILayout.Space(4); } } var infoStyle = new GUIStyle(EditorStyles.label); infoStyle.normal.textColor = new Color(0.7f, 0.7f, 0.7f); infoStyle.fontSize = 10; var infoText = $"{branch.lastCommitMessage} • {branch.lastCommitAuthor} • {FormatDate(branch.lastCommitDate)}"; EditorGUILayout.LabelField(infoText, infoStyle); } GUILayout.FlexibleSpace(); // Actions DrawBranchActions(branch); } } private void DrawBranchActions(BranchInfo branch) { using (new EditorGUILayout.VerticalScope(GUILayout.Width(100))) { if (!branch.isCurrent) { var checkoutStyle = new GUIStyle(GUI.skin.button); checkoutStyle.normal.textColor = accentColor; checkoutStyle.fixedHeight = 22; if (GUILayout.Button("CHECKOUT", checkoutStyle)) CheckoutBranch(branch); } if (!branch.isCurrent && !branch.isRemote) { var deleteStyle = new GUIStyle(GUI.skin.button); deleteStyle.normal.textColor = errorColor; deleteStyle.fixedHeight = 22; if (GUILayout.Button("DELETE", deleteStyle)) branchToDelete = branch; } } } private void DrawStashesTab() { EditorGUILayout.Space(8); if (stashes.Count == 0) { DrawEmptyStashState(); return; } using (var scrollView = new EditorGUILayout.ScrollViewScope(scrollPosition)) { scrollPosition = scrollView.scrollPosition; DrawCard(() => { foreach (var stash in stashes) { DrawStashItem(stash); if (stash != stashes.Last()) DrawSeparator(1f, 8f); } }, 12); } } private void DrawStashItem(StashInfo stash) { using (new EditorGUILayout.HorizontalScope()) { // Stash icon var iconSize = 32; var iconRect = GUILayoutUtility.GetRect(iconSize, iconSize); var iconTexture = CreateRoundedTexture(warningColor, iconSize, 6); GUI.DrawTexture(iconRect, iconTexture); var iconStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel); iconStyle.normal.textColor = Color.white; iconStyle.fontSize = 14; iconStyle.fontStyle = FontStyle.Bold; GUI.Label(iconRect, "📦", iconStyle); GUILayout.Space(12); // Stash info using (new EditorGUILayout.VerticalScope()) { var messageStyle = new GUIStyle(EditorStyles.boldLabel); messageStyle.fontSize = 13; messageStyle.normal.textColor = Color.white; EditorGUILayout.LabelField(stash.message, messageStyle); var detailStyle = new GUIStyle(EditorStyles.label); detailStyle.normal.textColor = new Color(0.7f, 0.7f, 0.7f); detailStyle.fontSize = 10; EditorGUILayout.LabelField($"Branch: {stash.branchName} • {stash.author} • {FormatDate(stash.date)}", detailStyle); // Changed files var filesText = $"{stash.changedFiles.Count} changed file{(stash.changedFiles.Count != 1 ? "s" : "")}"; EditorGUILayout.LabelField(filesText, detailStyle); } GUILayout.FlexibleSpace(); // Actions using (new EditorGUILayout.VerticalScope(GUILayout.Width(80))) { var applyStyle = new GUIStyle(GUI.skin.button); applyStyle.normal.textColor = successColor; applyStyle.fixedHeight = 22; if (GUILayout.Button("APPLY", applyStyle)) ApplyStash(stash); var deleteStyle = new GUIStyle(GUI.skin.button); deleteStyle.normal.textColor = errorColor; deleteStyle.fixedHeight = 22; if (GUILayout.Button("DELETE", deleteStyle)) stashToDelete = stash; } } } private void DrawEmptyStashState() { DrawCard(() => { var iconRect = GUILayoutUtility.GetRect(48, 48); var emptyTexture = CreateRoundedTexture(new Color(0.5f, 0.5f, 0.5f, 0.3f), 48, 12); GUI.DrawTexture(iconRect, emptyTexture); var iconStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel); iconStyle.normal.textColor = new Color(0.7f, 0.7f, 0.7f); iconStyle.fontSize = 20; GUI.Label(iconRect, "📦", iconStyle); EditorGUILayout.Space(8); var titleStyle = new GUIStyle(EditorStyles.boldLabel); titleStyle.alignment = TextAnchor.MiddleCenter; titleStyle.normal.textColor = new Color(0.7f, 0.7f, 0.7f); EditorGUILayout.LabelField("No Stashes", titleStyle); var subtitleStyle = new GUIStyle(EditorStyles.label); subtitleStyle.alignment = TextAnchor.MiddleCenter; subtitleStyle.normal.textColor = new Color(0.6f, 0.6f, 0.6f); subtitleStyle.wordWrap = true; EditorGUILayout.LabelField("Stash your work-in-progress changes to switch branches safely.", subtitleStyle); }, 32); } private void DrawDialogs() { if (showCreateBranchDialog) DrawCreateBranchDialog(); if (showStashDialog) DrawStashDialog(); if (branchToDelete != null) DrawDeleteBranchDialog(); if (stashToDelete != null) DrawDeleteStashDialog(); } private void DrawCreateBranchDialog() { var dialogRect = new Rect( (position.width - 300) * 0.5f, (position.height - 150) * 0.5f, 300, 150 ); EditorGUI.DrawRect(dialogRect, new Color(0, 0, 0, 0.7f)); GUILayout.BeginArea(dialogRect); DrawCard(() => { EditorGUILayout.LabelField("Create New Branch", EditorStyles.boldLabel); EditorGUILayout.Space(8); EditorGUILayout.LabelField("Branch name:"); newBranchName = EditorGUILayout.TextField(newBranchName); EditorGUILayout.Space(12); using (new EditorGUILayout.HorizontalScope()) { if (GUILayout.Button("Create")) { CreateBranch(newBranchName); showCreateBranchDialog = false; newBranchName = ""; } if (GUILayout.Button("Cancel")) { showCreateBranchDialog = false; newBranchName = ""; } } }, 12); GUILayout.EndArea(); } private void DrawStashDialog() { var dialogRect = new Rect( (position.width - 350) * 0.5f, (position.height - 180) * 0.5f, 350, 180 ); EditorGUI.DrawRect(dialogRect, new Color(0, 0, 0, 0.7f)); GUILayout.BeginArea(dialogRect); DrawCard(() => { EditorGUILayout.LabelField("Create Stash", EditorStyles.boldLabel); EditorGUILayout.Space(8); EditorGUILayout.LabelField("Stash message:"); stashMessage = EditorGUILayout.TextArea(stashMessage, GUILayout.Height(50)); EditorGUILayout.Space(12); using (new EditorGUILayout.HorizontalScope()) { if (GUILayout.Button("Create Stash")) { CreateStash(stashMessage); showStashDialog = false; stashMessage = ""; } if (GUILayout.Button("Cancel")) { showStashDialog = false; stashMessage = ""; } } }, 12); GUILayout.EndArea(); } private void DrawDeleteBranchDialog() { if (EditorUtility.DisplayDialog( "Delete Branch", $"Are you sure you want to delete branch '{branchToDelete.name}'?\n\nThis action cannot be undone.", "Delete", "Cancel")) { DeleteBranch(branchToDelete); } branchToDelete = null; } private void DrawDeleteStashDialog() { if (EditorUtility.DisplayDialog( "Delete Stash", $"Are you sure you want to delete stash '{stashToDelete.message}'?\n\nThis action cannot be undone.", "Delete", "Cancel")) { DeleteStash(stashToDelete); } stashToDelete = null; } #endregion #region Helper Methods private void DrawCard(System.Action content, int padding = 12) { var rect = EditorGUILayout.BeginVertical(); EditorGUI.DrawRect(rect, cardColor); GUILayout.Space(padding); EditorGUILayout.BeginHorizontal(); GUILayout.Space(padding); EditorGUILayout.BeginVertical(); content?.Invoke(); EditorGUILayout.EndVertical(); GUILayout.Space(padding); EditorGUILayout.EndHorizontal(); GUILayout.Space(padding); EditorGUILayout.EndVertical(); } private void DrawSeparator(float thickness = 1f, float spacing = 8f) { EditorGUILayout.Space(spacing); var rect = EditorGUILayout.GetControlRect(false, thickness); EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 0.3f)); EditorGUILayout.Space(spacing); } private void DrawCommitCountBadge(string text, Color color) { var badgeStyle = new GUIStyle(GUI.skin.box); badgeStyle.normal.textColor = Color.white; badgeStyle.normal.background = CreateRoundedTexture(color, 16, 8); badgeStyle.fontSize = 9; badgeStyle.padding = new RectOffset(6, 6, 2, 2); badgeStyle.margin = new RectOffset(0, 0, 0, 0); GUILayout.Label(text, badgeStyle); } private Texture2D CreateRoundedTexture(Color color, int size = 64, int cornerRadius = 8) { var key = $"{color}_{size}_{cornerRadius}"; if (textureCache.TryGetValue(key, out var cachedTexture) && cachedTexture != null) return cachedTexture; var texture = new Texture2D(size, size); var pixels = new Color[size * size]; for (int y = 0; y < size; y++) { for (int x = 0; x < size; x++) { float distanceToCorner = float.MaxValue; if (x < cornerRadius && y < cornerRadius) distanceToCorner = Vector2.Distance(new Vector2(x, y), new Vector2(cornerRadius, cornerRadius)); else if (x >= size - cornerRadius && y < cornerRadius) distanceToCorner = Vector2.Distance(new Vector2(x, y), new Vector2(size - cornerRadius - 1, cornerRadius)); else if (x < cornerRadius && y >= size - cornerRadius) distanceToCorner = Vector2.Distance(new Vector2(x, y), new Vector2(cornerRadius, size - cornerRadius - 1)); else if (x >= size - cornerRadius && y >= size - cornerRadius) distanceToCorner = Vector2.Distance(new Vector2(x, y), new Vector2(size - cornerRadius - 1, size - cornerRadius - 1)); pixels[y * size + x] = (distanceToCorner <= cornerRadius || (x >= cornerRadius && x < size - cornerRadius) || (y >= cornerRadius && y < size - cornerRadius)) ? color : Color.clear; } } texture.SetPixels(pixels); texture.Apply(); textureCache[key] = texture; return texture; } private string FormatDate(DateTime date) { var diff = DateTime.Now - date; if (diff.TotalMinutes < 1) return "just now"; if (diff.TotalMinutes < 60) return $"{(int)diff.TotalMinutes}m ago"; if (diff.TotalHours < 24) return $"{(int)diff.TotalHours}h ago"; if (diff.TotalDays < 7) return $"{(int)diff.TotalDays}d ago"; return date.ToString("MMM dd"); } private void ShowCreateBranchDialog() { showCreateBranchDialog = true; } private void ShowStashDialog() { showStashDialog = true; } #endregion #region Branch Operations private void CheckoutBranch(BranchInfo branch) { try { Debug.Log($"🔄 Checking out branch: {branch.name}"); // Update current branch foreach (var b in branches) b.isCurrent = false; branch.isCurrent = true; currentBranch = branch.name; EditorUtility.DisplayDialog("Branch Checkout", $"Successfully checked out branch '{branch.name}'", "OK"); Repaint(); } catch (Exception ex) { EditorUtility.DisplayDialog("Checkout Failed", $"Failed to checkout branch: {ex.Message}", "OK"); } } private void CreateBranch(string branchName) { if (string.IsNullOrWhiteSpace(branchName)) { EditorUtility.DisplayDialog("Invalid Branch Name", "Branch name cannot be empty.", "OK"); return; } if (branches.Any(b => b.name == branchName)) { EditorUtility.DisplayDialog("Branch Exists", $"A branch named '{branchName}' already exists.", "OK"); return; } var newBranch = new BranchInfo { name = branchName, isRemote = false, isCurrent = false, lastCommitMessage = "Branch created", lastCommitAuthor = "Developer", lastCommitDate = DateTime.Now, commitHash = Guid.NewGuid().ToString().Substring(0, 7), aheadCount = 0, behindCount = 0 }; branches.Insert(1, newBranch); // Insert after main branch Debug.Log($"✅ Created new branch: {branchName}"); EditorUtility.DisplayDialog("Branch Created", $"Successfully created branch '{branchName}'", "OK"); Repaint(); } private void DeleteBranch(BranchInfo branch) { branches.Remove(branch); Debug.Log($"🗑️ Deleted branch: {branch.name}"); EditorUtility.DisplayDialog("Branch Deleted", $"Successfully deleted branch '{branch.name}'", "OK"); Repaint(); } #endregion #region Stash Operations private void CreateStash(string message) { if (string.IsNullOrWhiteSpace(message)) message = "WIP: Stashed changes"; var newStash = new StashInfo { id = $"stash@{{{stashes.Count}}}", message = message, branchName = currentBranch, author = "Developer", date = DateTime.Now, changedFiles = new List { "Assets/Scripts/TempFile.cs" } // Mock changed files }; stashes.Insert(0, newStash); Debug.Log($"📦 Created stash: {message}"); EditorUtility.DisplayDialog("Stash Created", $"Successfully stashed changes: '{message}'", "OK"); Repaint(); } private void ApplyStash(StashInfo stash) { Debug.Log($"📥 Applying stash: {stash.message}"); EditorUtility.DisplayDialog("Stash Applied", $"Successfully applied stash: '{stash.message}'", "OK"); // In real implementation, this would apply the stashed changes // For now, we'll just show the dialog Repaint(); } private void DeleteStash(StashInfo stash) { stashes.Remove(stash); Debug.Log($"🗑️ Deleted stash: {stash.message}"); EditorUtility.DisplayDialog("Stash Deleted", $"Successfully deleted stash: '{stash.message}'", "OK"); Repaint(); } #endregion } #region Data Models [Serializable] public class BranchInfo { public string name; public bool isRemote; public bool isCurrent; public string lastCommitMessage; public string lastCommitAuthor; public DateTime lastCommitDate; public string commitHash; public int aheadCount; public int behindCount; } [Serializable] public class StashInfo { public string id; public string message; public string branchName; public string author; public DateTime date; public List changedFiles; } #endregion }