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
}