This commit is contained in:
Даниил Заикин
2026-06-17 20:44:53 +03:00
parent 9d153773c2
commit b133e7c656
1880 changed files with 244545 additions and 0 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b520f278fadc2ac4fa9f2f58770e8b12
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,411 @@
#if UNITY_EDITOR
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
[InitializeOnLoad]
public static class GitBranchToolbar
{
private const string ToolbarElementName = "GitBranchToolbar";
private const double RefreshInterval = 2.0;
private const double ToolbarCheckInterval = 0.25;
private const float Height = 18f;
private static VisualElement toolbarElement;
private static string cachedBranchName = "...";
private static GitState cachedState = GitState.Unknown;
private static double nextRefreshTime;
private static double nextToolbarCheckTime;
private enum GitState
{
Unknown,
NoRepository,
Branch,
DetachedHead
}
static GitBranchToolbar()
{
RefreshBranchInfo(force: true);
EditorApplication.update += OnEditorUpdate;
EditorApplication.delayCall += AddToolbarUI;
AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload;
}
private static void OnAfterAssemblyReload()
{
EditorApplication.delayCall += AddToolbarUI;
}
private static void OnEditorUpdate()
{
if (EditorApplication.timeSinceStartup >= nextRefreshTime)
{
RefreshBranchInfo();
}
EnsureToolbarExists();
}
private static void EnsureToolbarExists()
{
if (EditorApplication.timeSinceStartup < nextToolbarCheckTime)
return;
nextToolbarCheckTime = EditorApplication.timeSinceStartup + ToolbarCheckInterval;
var toolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar");
if (toolbarType == null)
return;
var toolbars = Resources.FindObjectsOfTypeAll(toolbarType);
if (toolbars == null || toolbars.Length == 0)
return;
foreach (var toolbar in toolbars)
{
TryInjectIntoToolbar(toolbarType, toolbar);
}
}
private static void AddToolbarUI()
{
var toolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar");
if (toolbarType == null)
return;
var toolbars = Resources.FindObjectsOfTypeAll(toolbarType);
if (toolbars == null || toolbars.Length == 0)
return;
foreach (var toolbar in toolbars)
{
TryInjectIntoToolbar(toolbarType, toolbar);
}
}
private static void TryInjectIntoToolbar(Type toolbarType, UnityEngine.Object toolbar)
{
var rootField = toolbarType.GetField("m_Root", BindingFlags.NonPublic | BindingFlags.Instance);
if (rootField == null)
return;
if (rootField.GetValue(toolbar) is not VisualElement root)
return;
var rightContainer = root.Q("ToolbarZoneRightAlign");
if (rightContainer == null)
return;
var existing = rightContainer.Q<IMGUIContainer>(ToolbarElementName);
if (existing != null)
{
toolbarElement = existing;
return;
}
var newElement = new IMGUIContainer(DrawGUI);
newElement.name = ToolbarElementName;
newElement.style.flexShrink = 0;
newElement.style.flexGrow = 0;
newElement.style.minWidth = 70;
newElement.style.maxWidth = 130;
newElement.style.marginLeft = 4;
newElement.style.marginRight = 0;
newElement.style.overflow = Overflow.Hidden;
newElement.style.alignSelf = Align.FlexEnd;
rightContainer.Add(newElement);
toolbarElement = newElement;
}
private static void DrawGUI()
{
RefreshBranchInfo();
var indicatorColor = GetBackgroundColor(cachedState, cachedBranchName);
var textColor = GetTextColor(cachedState);
string displayText = GetShortBranchName(cachedBranchName, cachedState);
Rect rect = GUILayoutUtility.GetRect(
GUIContent.none,
GUIStyle.none,
GUILayout.Height(Height),
GUILayout.MinWidth(80),
GUILayout.MaxWidth(220)
);
const float dotSize = 8f;
const float leftPadding = 4f;
const float gap = 6f;
const float rightPadding = 0f;
Rect dotRect = new Rect(
rect.x + leftPadding,
rect.y + Mathf.Round((rect.height - dotSize) * 0.5f),
dotSize,
dotSize
);
Rect labelRect = new Rect(
dotRect.xMax + gap,
rect.y,
rect.width - leftPadding - dotSize - gap - rightPadding,
rect.height
);
GUI.Label(
labelRect,
new GUIContent(displayText, cachedBranchName),
GetLabelStyle(textColor)
);
EditorGUI.DrawRect(dotRect, indicatorColor);
EditorGUIUtility.AddCursorRect(rect, MouseCursor.Link);
if (Event.current.type == EventType.MouseDown && rect.Contains(Event.current.mousePosition))
{
ShowContextMenu();
Event.current.Use();
}
}
private static string GetShortBranchName(string branchName, GitState state)
{
if (state == GitState.DetachedHead)
return "DETACHED";
if (state == GitState.NoRepository)
return "No Repo";
if (state == GitState.Unknown)
return "...";
if (string.IsNullOrWhiteSpace(branchName))
return "...";
const int maxLen = 18;
if (branchName.Length <= maxLen)
return branchName;
return branchName.Substring(0, maxLen - 1) + "…";
}
private static void DrawBackground(Rect rect, Color color)
{
var oldColor = GUI.color;
GUI.color = color;
GUI.Box(rect, GUIContent.none, EditorStyles.toolbarButton);
GUI.color = oldColor;
}
private static GUIStyle GetLabelStyle(Color textColor)
{
var style = new GUIStyle(EditorStyles.label)
{
alignment = TextAnchor.MiddleLeft,
fontStyle = FontStyle.Bold,
clipping = TextClipping.Clip,
padding = new RectOffset(0, 0, 0, 0),
margin = new RectOffset(0, 0, 0, 0),
fixedHeight = (int)Height
};
style.normal.textColor = textColor;
style.hover.textColor = textColor;
style.active.textColor = textColor;
style.focused.textColor = textColor;
return style;
}
private static GUIContent GetDisplayContent()
{
return cachedState switch
{
GitState.Branch => new GUIContent($"{cachedBranchName + "TEST TEST TEST"}", cachedBranchName),
GitState.DetachedHead => new GUIContent("DETACHED"),
GitState.NoRepository => new GUIContent("No Repo"),
_ => new GUIContent("...")
};
}
private static Color GetBackgroundColor(GitState state, string branchName)
{
string normalized = (branchName ?? string.Empty).Trim();
return state switch
{
GitState.NoRepository => new Color(0.25f, 0.25f, 0.25f, 1f),
GitState.DetachedHead => new Color(0.45f, 0.30f, 0.10f, 1f),
GitState.Branch when normalized.Equals("main", StringComparison.OrdinalIgnoreCase)
=> Color.red,
GitState.Branch when normalized.Equals("develop", StringComparison.OrdinalIgnoreCase)
=> Color.blue,
GitState.Branch when normalized.StartsWith("feature-", StringComparison.OrdinalIgnoreCase)
=> Color.green,
GitState.Branch when normalized.StartsWith("feature/", StringComparison.OrdinalIgnoreCase)
=> new Color(0.12f, 0.28f, 0.45f, 1f),
GitState.Branch when normalized.StartsWith("hotfix-", StringComparison.OrdinalIgnoreCase)
=> Color.yellow,
GitState.Branch when normalized.StartsWith("hotfix/", StringComparison.OrdinalIgnoreCase)
=> new Color(0.45f, 0.20f, 0.10f, 1f),
GitState.Branch when normalized.StartsWith("bugfix-", StringComparison.OrdinalIgnoreCase)
=> Color.gray,
GitState.Branch => new Color(0.18f, 0.35f, 0.22f, 1f),
_ => new Color(0.22f, 0.22f, 0.22f, 1f)
};
}
private static Color GetTextColor(GitState state)
{
return state switch
{
GitState.NoRepository => new Color(0.85f, 0.85f, 0.85f),
_ => Color.white
};
}
private static void ShowContextMenu()
{
var menu = new GenericMenu();
menu.AddDisabledItem(new GUIContent($"Branch: {cachedBranchName}"));
if (cachedState == GitState.Branch || cachedState == GitState.DetachedHead)
{
menu.AddItem(new GUIContent("Copy Branch Name"), false, () =>
{
EditorGUIUtility.systemCopyBuffer = cachedBranchName;
Debug.Log($"Copied Git branch name: {cachedBranchName}");
});
}
else
{
menu.AddDisabledItem(new GUIContent("Copy Branch Name"));
}
menu.AddSeparator("");
menu.AddItem(new GUIContent("Refresh"), false, () =>
{
RefreshBranchInfo(force: true);
});
menu.ShowAsContext();
}
private static void RefreshBranchInfo(bool force = false)
{
if (!force && EditorApplication.timeSinceStartup < nextRefreshTime)
return;
nextRefreshTime = EditorApplication.timeSinceStartup + RefreshInterval;
try
{
string projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
string gitDir = FindGitDirectory(projectRoot);
if (string.IsNullOrEmpty(gitDir))
{
cachedBranchName = "No Git";
cachedState = GitState.NoRepository;
return;
}
string headPath = Path.Combine(gitDir, "HEAD");
if (!File.Exists(headPath))
{
cachedBranchName = "No Git";
cachedState = GitState.NoRepository;
return;
}
string headContent = File.ReadAllText(headPath).Trim();
if (headContent.StartsWith("ref:", StringComparison.OrdinalIgnoreCase))
{
const string headsPrefix = "refs/heads/";
int headsIndex = headContent.IndexOf(headsPrefix, StringComparison.OrdinalIgnoreCase);
if (headsIndex >= 0)
{
cachedBranchName = headContent.Substring(headsIndex + headsPrefix.Length).Trim();
}
else
{
cachedBranchName = headContent
.Replace("ref:", string.Empty)
.Trim()
.Split('/')
.LastOrDefault() ?? "unknown";
}
cachedState = GitState.Branch;
}
else
{
cachedBranchName = "DETACHED";
cachedState = GitState.DetachedHead;
}
}
catch
{
cachedBranchName = "Git ?";
cachedState = GitState.Unknown;
}
}
private static string FindGitDirectory(string startDirectory)
{
var dir = new DirectoryInfo(startDirectory);
while (dir != null)
{
string gitPath = Path.Combine(dir.FullName, ".git");
if (Directory.Exists(gitPath))
return gitPath;
if (File.Exists(gitPath))
{
string content = File.ReadAllText(gitPath).Trim();
const string prefix = "gitdir:";
if (content.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
string relativePath = content.Substring(prefix.Length).Trim();
string resolvedPath = Path.GetFullPath(Path.Combine(dir.FullName, relativePath));
if (Directory.Exists(resolvedPath))
return resolvedPath;
}
}
dir = dir.Parent;
}
return null;
}
}
#endif
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9e93a5acbb3e741409543d32ad2762d0