18 Commits

Author SHA1 Message Date
MrUnknownDE 7bc0eee024 add git console 2026-04-07 13:05:14 +02:00
MrUnknownDE 48134f2a03 add pull button 2026-04-07 13:02:27 +02:00
MrUnknownDE 00ab946aa0 add unity settings check 2026-04-07 11:28:54 +02:00
MrUnknownDE 992e67eee2 Update Readme.md 2026-04-07 11:19:18 +02:00
MrUnknownDE e247d4ea4d add git web url override 2026-04-07 11:14:32 +02:00
MrUnknownDE 0955383fb2 add the german law 2026-04-07 11:07:36 +02:00
MrUnknownDE 8450f0ccbb Update Readme.md 2026-04-07 10:58:50 +02:00
MrUnknownDE 99e6d462cf add init check and revert button 2026-04-07 10:58:07 +02:00
MrUnknownDE a68f45734b ähm merg from old branch 2026-04-06 20:02:28 +02:00
MrUnknownDE df4ade6977 add staging system 2026-04-06 18:39:24 +02:00
MrUnknownDE 889b677830 remove broken version 2026-04-06 18:38:14 +02:00
MrUnknownDE 8c0935ac92 update index.json 2026-04-06 18:32:04 +02:00
MrUnknownDE 467f665303 rename repo "again" ^^ 2026-04-06 18:19:12 +02:00
MrUnknownDE a0db61aa07 add workflow for vcc release 2026-04-06 18:10:37 +02:00
MrUnknownDE 8a96aabad1 add assembly definition 2026-04-06 18:01:39 +02:00
MrUnknownDE 545e48e2e4 add custom domain 2026-04-06 17:52:29 +02:00
MrUnknownDE 27a11704eb Create CNAME 2026-04-06 17:50:47 +02:00
MrUnknownDE dba802bf58 add vcc-index.json 2026-04-06 17:49:19 +02:00
5 changed files with 595 additions and 189 deletions
+194
View File
@@ -0,0 +1,194 @@
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using System;
using System.IO.Pipes;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
public class DiscordRPCPanel : EditorWindow
{
// === HIER DEINE DISCORD APPLICATION ID EINTRAGEN ===
private const string clientId = "1490767097096048780";
private static bool isEnabled = false;
private static bool hideSceneName = false;
private static string customStatus = "Building a VRChat World/Avatar";
private static NamedPipeClientStream pipe;
private static long startTime;
[MenuItem("Tools/MrUnknownDE/Discord RPC")]
public static void ShowWindow()
{
DiscordRPCPanel window = GetWindow<DiscordRPCPanel>("Discord RPC");
window.minSize = new Vector2(300, 250);
}
private void OnEnable()
{
// Lädt gespeicherte Einstellungen
isEnabled = EditorPrefs.GetBool("DiscordRPC_Enabled", false);
hideSceneName = EditorPrefs.GetBool("DiscordRPC_HideScene", false);
customStatus = EditorPrefs.GetString("DiscordRPC_Status", "Building a VRChat World");
if (isEnabled) ConnectToDiscord();
// Hooks in Unity einhängen
EditorApplication.playModeStateChanged -= OnPlayModeChanged;
EditorApplication.playModeStateChanged += OnPlayModeChanged;
EditorSceneManager.sceneOpened -= OnSceneOpened;
EditorSceneManager.sceneOpened += OnSceneOpened;
}
private void OnDisable()
{
if (pipe != null && pipe.IsConnected) pipe.Close();
}
private void OnGUI()
{
GUILayout.Space(10);
GUILayout.Label("DISCORD RICH PRESENCE", EditorStyles.boldLabel);
GUILayout.Space(5);
EditorGUI.BeginChangeCheck();
// Status Toggle
GUI.backgroundColor = isEnabled ? new Color(0.2f, 0.8f, 0.2f) : new Color(0.8f, 0.2f, 0.2f);
if (GUILayout.Button(isEnabled ? "Status: ONLINE" : "Status: OFFLINE", GUILayout.Height(30)))
{
isEnabled = !isEnabled;
EditorPrefs.SetBool("DiscordRPC_Enabled", isEnabled);
if (isEnabled) ConnectToDiscord();
else DisconnectDiscord();
}
GUI.backgroundColor = Color.white;
GUILayout.Space(15);
GUILayout.Label("SETTINGS", EditorStyles.boldLabel);
// Custom Status Input
GUILayout.Label("Custom Status (Line 1):");
customStatus = EditorGUILayout.TextField(customStatus);
// Privacy Toggle
hideSceneName = EditorGUILayout.Toggle("Hide Scene Name (Privacy)", hideSceneName);
if (EditorGUI.EndChangeCheck())
{
EditorPrefs.SetBool("DiscordRPC_HideScene", hideSceneName);
EditorPrefs.SetString("DiscordRPC_Status", customStatus);
if (isEnabled) UpdatePresence();
}
GUILayout.Space(15);
EditorGUILayout.HelpBox("Wenn aktiv, sieht dein Discord-Server in deinem Profil, an welcher Szene du gerade baust.", MessageType.Info);
}
// --- UNITY EVENT HOOKS ---
private void OnPlayModeChanged(PlayModeStateChange state)
{
if (isEnabled) UpdatePresence();
}
private void OnSceneOpened(UnityEngine.SceneManagement.Scene scene, OpenSceneMode mode)
{
if (isEnabled) UpdatePresence();
}
// --- DISCORD IPC LOGIK ---
private async void ConnectToDiscord()
{
if (pipe != null && pipe.IsConnected) return;
try
{
pipe = new NamedPipeClientStream(".", "discord-ipc-0", PipeDirection.InOut, PipeOptions.Asynchronous);
await pipe.ConnectAsync(2000);
// Handshake (Opcode 0)
string handshake = "{\"v\": 1, \"client_id\": \"" + clientId + "\"}";
SendFrame(0, handshake);
startTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
UpdatePresence();
UnityEngine.Debug.Log("Discord RPC Connected!");
}
catch (Exception)
{
UnityEngine.Debug.LogWarning("Discord RPC: Konnte keine Verbindung zu Discord herstellen. Läuft Discord?");
isEnabled = false;
}
}
private void DisconnectDiscord()
{
if (pipe != null && pipe.IsConnected)
{
pipe.Close();
pipe.Dispose();
}
pipe = null;
UnityEngine.Debug.Log("Discord RPC Disconnected.");
}
private void UpdatePresence()
{
if (pipe == null || !pipe.IsConnected) return;
string sceneName = hideSceneName ? "Secret Map" : EditorSceneManager.GetActiveScene().name;
if (string.IsNullOrEmpty(sceneName)) sceneName = "Unsaved Scene";
string stateText = EditorApplication.isPlaying ? "Testet im Playmode" : $"Editiert: {sceneName}.unity";
// Flieht die Strings für sicheres JSON
string safeStatus = customStatus.Replace("\"", "\\\"");
string safeState = stateText.Replace("\"", "\\\"");
// Das Activity Payload (Opcode 1)
string json = $@"{{
""cmd"": ""SET_ACTIVITY"",
""args"": {{
""pid"": {Process.GetCurrentProcess().Id},
""activity"": {{
""details"": ""{safeStatus}"",
""state"": ""{safeState}"",
""timestamps"": {{
""start"": {startTime}
}},
""instance"": false
}}
}},
""nonce"": ""1""
}}";
SendFrame(1, json);
}
private void SendFrame(int opcode, string payload)
{
if (pipe == null || !pipe.IsConnected) return;
byte[] payloadBytes = Encoding.UTF8.GetBytes(payload);
int length = payloadBytes.Length;
byte[] buffer = new byte[8 + length];
BitConverter.GetBytes(opcode).CopyTo(buffer, 0);
BitConverter.GetBytes(length).CopyTo(buffer, 4);
payloadBytes.CopyTo(buffer, 8);
try
{
pipe.Write(buffer, 0, buffer.Length);
}
catch (Exception e)
{
UnityEngine.Debug.LogError("Discord RPC Error: " + e.Message);
DisconnectDiscord();
}
}
}
+323 -151
View File
@@ -7,45 +7,149 @@ using System.Collections.Generic;
public class GitPanel : EditorWindow public class GitPanel : EditorWindow
{ {
private string commitMessage = ""; private string commitMessage = "";
private string remoteUrlInput = "";
private string newBranchName = "";
private bool isGitInstalled = true;
private bool hasRepo = false; private bool hasRepo = false;
private bool settingsCorrect = true;
private string settingsWarning = "";
private string currentBranchName = "unknown";
private string[] availableBranches = new string[0];
private int selectedBranchIndex = 0;
private string[] changedFiles = new string[0]; private string[] changedFiles = new string[0];
private Vector2 scrollPositionChanges; private Vector2 scrollPositionChanges;
private Vector2 scrollPositionHistory; private Vector2 scrollPositionHistory;
// STATISCH: Damit RunGitCommand darauf zugreifen kann und das Log beim Neuladen erhalten bleibt!
private static string gitLogOutput = "";
private Vector2 scrollPositionLog;
private int selectedTab = 0; private int selectedTab = 0;
private string[] tabNames = { "Changes", "History" }; private string[] tabNames = { "Changes", "History" };
private bool showSettings = false;
private string webUrlOverride = "";
private string prefsKey = "";
private struct CommitInfo { public string hash; public string date; public string message; } private struct CommitInfo { public string hash; public string date; public string message; }
private List<CommitInfo> commitHistory = new List<CommitInfo>(); private List<CommitInfo> commitHistory = new List<CommitInfo>();
[MenuItem("Tools/Git-Tool")] [MenuItem("Tools/MrUnknownDE/GIT Version Control")]
public static void ShowWindow() public static void ShowWindow()
{ {
GitPanel window = GetWindow<GitPanel>("Source Control"); GitPanel window = GetWindow<GitPanel>("GIT Version Control System");
window.minSize = new Vector2(350, 500); window.minSize = new Vector2(380, 650);
} }
private void OnEnable() private void OnEnable()
{
prefsKey = $"GitTool_WebUrl_{Application.dataPath.GetHashCode()}";
webUrlOverride = EditorPrefs.GetString(prefsKey, "");
RefreshData();
}
private void OnFocus() { RefreshData(); }
public void RefreshData()
{ {
CheckGitInstallation();
CheckUnitySettings();
if (!isGitInstalled) return;
CheckRepoStatus(); CheckRepoStatus();
SetDefaultCommitMessage();
if (hasRepo)
{
currentBranchName = RunGitCommand("rev-parse --abbrev-ref HEAD").Trim();
FetchBranches();
}
if (string.IsNullOrWhiteSpace(commitMessage) || commitMessage.StartsWith("Auto-Save:"))
{
SetDefaultCommitMessage();
}
Repaint();
} }
private void SetDefaultCommitMessage() private void CheckGitInstallation()
{ {
commitMessage = $"Auto-Save: {System.DateTime.Now:yyyy-MM-dd HH:mm:ss}"; try {
ProcessStartInfo startInfo = new ProcessStartInfo("git", "--version") { UseShellExecute = false, RedirectStandardOutput = true, CreateNoWindow = true };
using (Process p = Process.Start(startInfo)) { p.WaitForExit(); isGitInstalled = true; }
} catch { isGitInstalled = false; }
} }
// FIX: Nutzt jetzt die aktuelle Unity-API (VersionControlSettings.mode)
private void CheckUnitySettings()
{
settingsCorrect = true;
settingsWarning = "";
if (VersionControlSettings.mode != "Visible Meta Files")
{
settingsCorrect = false;
settingsWarning += "• Version Control Mode must be 'Visible Meta Files'\n";
}
if (EditorSettings.serializationMode != SerializationMode.ForceText)
{
settingsCorrect = false;
settingsWarning += "• Asset Serialization must be 'Force Text'\n";
}
}
private void FixUnitySettings()
{
VersionControlSettings.mode = "Visible Meta Files";
EditorSettings.serializationMode = SerializationMode.ForceText;
UnityEngine.Debug.Log("Git-Tool: Unity Project Settings updated for Git compatibility.");
RefreshData();
}
private void SetDefaultCommitMessage() { commitMessage = $"Auto-Save: {System.DateTime.Now:yyyy-MM-dd HH:mm:ss}"; }
private void OnGUI() private void OnGUI()
{ {
GUILayout.Space(10); GUILayout.Space(10);
GUILayout.Label("SOURCE CONTROL", EditorStyles.boldLabel); GUILayout.Label("GIT Version Control System", EditorStyles.boldLabel);
if (hasRepo) GUILayout.Label($"Active Branch: {currentBranchName}", EditorStyles.miniLabel);
GUILayout.Space(5); GUILayout.Space(5);
if (!hasRepo) if (!settingsCorrect)
{ {
RenderInitUI(); EditorGUILayout.BeginVertical("box");
return; EditorGUILayout.HelpBox("INCOMPATIBLE PROJECT SETTINGS:\n" + settingsWarning, MessageType.Error);
GUI.backgroundColor = new Color(1f, 0.5f, 0f);
if (GUILayout.Button("Fix Project Settings Now"))
{
FixUnitySettings();
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndVertical();
GUILayout.Space(10);
}
if (!isGitInstalled) { RenderGitMissingUI(); return; }
if (!hasRepo) { RenderInitUI(); return; }
showSettings = EditorGUILayout.Foldout(showSettings, "⚙️ Repository Settings");
if (showSettings)
{
EditorGUILayout.BeginVertical("box");
GUILayout.Label("Web Override (For custom SSH instances)", EditorStyles.miniBoldLabel);
EditorGUI.BeginChangeCheck();
webUrlOverride = EditorGUILayout.TextField("Web URL:", webUrlOverride);
if (EditorGUI.EndChangeCheck())
{
EditorPrefs.SetString(prefsKey, webUrlOverride.Trim());
}
EditorGUILayout.EndVertical();
GUILayout.Space(5);
} }
selectedTab = GUILayout.Toolbar(selectedTab, tabNames, GUILayout.Height(25)); selectedTab = GUILayout.Toolbar(selectedTab, tabNames, GUILayout.Height(25));
@@ -55,224 +159,292 @@ public class GitPanel : EditorWindow
else RenderHistoryUI(); else RenderHistoryUI();
} }
private void RenderGitMissingUI()
{
EditorGUILayout.HelpBox("CRITICAL: Git not found.", MessageType.Error);
if (GUILayout.Button("Download Git for Windows", GUILayout.Height(30))) Application.OpenURL("https://git-scm.com/download/win");
}
private void RenderInitUI() private void RenderInitUI()
{ {
EditorGUILayout.HelpBox("No local Git repository found. Initialize current project folder?", MessageType.Warning); EditorGUILayout.HelpBox("No local Git repository found.", MessageType.Warning);
remoteUrlInput = EditorGUILayout.TextField("Remote URL:", remoteUrlInput);
if (GUILayout.Button("Initialize Repository", GUILayout.Height(30))) if (GUILayout.Button("Initialize Repository", GUILayout.Height(30)))
{ {
RunGitCommand("init"); RunGitCommand("init", true);
RunGitCommand("branch -M main", true);
if (!string.IsNullOrWhiteSpace(remoteUrlInput)) {
RunGitCommand($"remote add origin \"{remoteUrlInput.Trim()}\"", true);
RunGitCommand("pull origin main --allow-unrelated-histories --no-edit", true);
}
GenerateUnityGitIgnore(); GenerateUnityGitIgnore();
RunGitCommand("add .gitignore"); AssetDatabase.Refresh();
RunGitCommand("commit -m \"Initial commit (GitIgnore)\""); RunGitCommand("add .gitignore", true);
CheckRepoStatus(); RunGitCommand("commit -m \"Initial commit (GitIgnore)\"", true);
if (!string.IsNullOrWhiteSpace(remoteUrlInput)) RunGitCommand("push -u origin main", true);
RefreshData();
} }
} }
private void RenderGitUI() private void RenderGitUI()
{ {
commitMessage = EditorGUILayout.TextField(commitMessage, GUILayout.Height(25)); EditorGUILayout.BeginVertical("box");
GUILayout.Label("Branch Management", EditorStyles.boldLabel);
GUI.backgroundColor = new Color(0.2f, 0.4f, 0.8f);
if (GUILayout.Button("✓ Commit & Push", GUILayout.Height(30))) if (availableBranches.Length > 0)
{ {
if (string.IsNullOrWhiteSpace(commitMessage)) commitMessage = $"Auto-Save: {System.DateTime.Now:yyyy-MM-dd HH:mm:ss}"; EditorGUI.BeginChangeCheck();
int newIndex = EditorGUILayout.Popup("Switch Branch:", selectedBranchIndex, availableBranches);
RunGitCommand("add ."); if (EditorGUI.EndChangeCheck() && newIndex != selectedBranchIndex)
RunGitCommand($"commit -m \"{commitMessage}\""); {
RunGitCommand("push"); RunGitCommand($"checkout \"{availableBranches[newIndex]}\"", true);
AssetDatabase.Refresh();
SetDefaultCommitMessage(); RefreshData();
CheckRepoStatus(); return;
UnityEngine.Debug.Log("Git-Tool: Changes successfully pushed!"); }
} }
GUI.backgroundColor = Color.white;
GUILayout.Space(10);
EditorGUILayout.HelpBox("Legend: [M] Modified | [A] Added | [D] Deleted | [??] Untracked", MessageType.Info);
GUILayout.Space(5);
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
GUILayout.Label($"CHANGES ({changedFiles.Length})", EditorStyles.boldLabel); newBranchName = EditorGUILayout.TextField("New Branch:", newBranchName);
if (GUILayout.Button("↻", GUILayout.Width(25))) GUI.backgroundColor = new Color(0.2f, 0.6f, 0.2f);
if (GUILayout.Button("+ Create", GUILayout.Width(80)))
{ {
CheckRepoStatus(); if (!string.IsNullOrWhiteSpace(newBranchName))
SetDefaultCommitMessage(); {
RunGitCommand($"checkout -b \"{newBranchName.Trim()}\"", true);
newBranchName = "";
RefreshData();
GUI.FocusControl(null);
return;
}
} }
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
GUILayout.Space(10);
commitMessage = EditorGUILayout.TextField(commitMessage, GUILayout.Height(25));
EditorGUILayout.BeginHorizontal();
GUI.backgroundColor = new Color(0.2f, 0.4f, 0.8f);
if (GUILayout.Button("✓ Push", GUILayout.Height(30)))
{
UnityEngine.Debug.Log("Git-Tool: Saving Scenes and Assets before push...");
UnityEditor.SceneManagement.EditorSceneManager.SaveOpenScenes();
AssetDatabase.SaveAssets();
if (string.IsNullOrWhiteSpace(commitMessage)) SetDefaultCommitMessage();
RunGitCommand("add .", true);
RunGitCommand($"commit -m \"{commitMessage}\"", true);
string pushResult = RunGitCommand("push -u origin HEAD", true);
if (pushResult.Contains("rejected") || pushResult.Contains("fetch first"))
{
UnityEngine.Debug.LogError("Git-Tool: PUSH REJECTED! Jemand anderes hat Änderungen hochgeladen. Bitte klicke zuerst auf 'Pull'.");
}
else
{
UnityEngine.Debug.Log("Git-Tool: Changes successfully pushed!");
commitMessage = "";
}
RefreshData();
}
GUI.backgroundColor = new Color(0.8f, 0.6f, 0.2f);
if (GUILayout.Button("⬇️ Pull", GUILayout.Width(80), GUILayout.Height(30)))
{
UnityEditor.SceneManagement.EditorSceneManager.SaveOpenScenes();
AssetDatabase.SaveAssets();
string pullResult = RunGitCommand("pull", true);
AssetDatabase.Refresh();
if (pullResult.Contains("CONFLICT"))
{
UnityEngine.Debug.LogError("Git-Tool: MERGE CONFLICT! Bitte in VS Code auflösen!");
EditorUtility.DisplayDialog("Merge Conflict", "Es gibt Konflikte mit den Server-Daten!\n\nGit konnte die Änderungen nicht automatisch zusammenführen. Bitte öffne die roten Dateien in deinem Code-Editor und löse den Konflikt manuell auf.", "OK");
}
RefreshData();
}
GUI.backgroundColor = new Color(0.8f, 0.3f, 0.3f);
if (GUILayout.Button("⎌ Revert", GUILayout.Width(80), GUILayout.Height(30)))
{
if (EditorUtility.DisplayDialog("Revert Changes?", "Discard ALL uncommitted changes?", "Yes", "Cancel")) {
RunGitCommand("reset --hard HEAD", true);
RunGitCommand("clean -fd", true);
AssetDatabase.Refresh();
RefreshData();
}
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
GUILayout.Space(10);
EditorGUILayout.BeginHorizontal();
GUILayout.Label($"CHANGES ({changedFiles.Length})", EditorStyles.boldLabel);
if (GUILayout.Button("↻", GUILayout.Width(25))) RefreshData();
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
scrollPositionChanges = EditorGUILayout.BeginScrollView(scrollPositionChanges, "box"); scrollPositionChanges = EditorGUILayout.BeginScrollView(scrollPositionChanges, "box");
if (changedFiles.Length == 0) GUILayout.Label("No changes.");
if (changedFiles.Length == 0) GUILayout.Label("No unsaved changes.");
else RenderFileList(changedFiles); else RenderFileList(changedFiles);
EditorGUILayout.EndScrollView();
GUILayout.Space(5);
EditorGUILayout.BeginHorizontal();
GUILayout.Label("GIT CONSOLE", EditorStyles.boldLabel);
if (GUILayout.Button("Clear", GUILayout.Width(50))) gitLogOutput = "";
EditorGUILayout.EndHorizontal();
scrollPositionLog = EditorGUILayout.BeginScrollView(scrollPositionLog, "box", GUILayout.Height(120));
GUIStyle logStyle = new GUIStyle(EditorStyles.label) { wordWrap = true, fontSize = 10 };
GUILayout.Label(string.IsNullOrEmpty(gitLogOutput) ? "Ready." : gitLogOutput, logStyle);
EditorGUILayout.EndScrollView(); EditorGUILayout.EndScrollView();
} }
private void RenderHistoryUI() private void RenderHistoryUI()
{ {
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
GUILayout.Label("LAST COMMITS (Click to open in Browser)", EditorStyles.boldLabel); GUILayout.Label("LAST COMMITS", EditorStyles.boldLabel);
if (GUILayout.Button("↻", GUILayout.Width(25))) FetchHistory(); if (GUILayout.Button("↻", GUILayout.Width(25))) FetchHistory();
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
GUILayout.Space(5);
if (commitHistory.Count == 0)
{
GUILayout.Label("No commits found.");
return;
}
scrollPositionHistory = EditorGUILayout.BeginScrollView(scrollPositionHistory, "box"); scrollPositionHistory = EditorGUILayout.BeginScrollView(scrollPositionHistory, "box");
foreach (var commit in commitHistory) {
foreach (var commit in commitHistory)
{
Rect rect = EditorGUILayout.GetControlRect(false, 22); Rect rect = EditorGUILayout.GetControlRect(false, 22);
if (rect.Contains(Event.current.mousePosition)) EditorGUI.DrawRect(rect, new Color(1f, 1f, 1f, 0.1f));
if (rect.Contains(Event.current.mousePosition)) GUI.Label(rect, $"<b>{commit.hash}</b> | {commit.date} | {commit.message}", new GUIStyle(EditorStyles.label){richText=true});
{ if (Event.current.type == EventType.MouseDown && rect.Contains(Event.current.mousePosition)) {
EditorGUI.DrawRect(rect, new Color(1f, 1f, 1f, 0.1f));
}
GUIStyle textStyle = new GUIStyle(EditorStyles.label) { richText = true };
GUI.Label(rect, $"<b>{commit.hash}</b> | {commit.date} | {commit.message}", textStyle);
Event e = Event.current;
if (e.type == EventType.MouseDown && e.button == 0 && rect.Contains(e.mousePosition))
{
OpenCommitInBrowser(commit.hash); OpenCommitInBrowser(commit.hash);
e.Use(); Event.current.Use();
} }
GUILayout.Space(2);
} }
EditorGUILayout.EndScrollView(); EditorGUILayout.EndScrollView();
} }
private void RenderFileList(string[] files) private void RenderFileList(string[] files)
{ {
foreach (string line in files) foreach (string line in files) {
{
if (line.Length < 4) continue; if (line.Length < 4) continue;
string status = line.Substring(0, 2).Trim();
string status = line.Substring(0, 1).Trim() == "" ? line.Substring(0, 2) : line.Substring(0, 1); string path = line.Substring(3).Trim().Replace("\"", "");
string path = line.Substring(line.IndexOf('\t') + 1 > 0 ? line.IndexOf('\t') + 1 : 3).Trim();
if (path.StartsWith("\"") && path.EndsWith("\"")) path = path.Substring(1, path.Length - 2);
Rect rect = EditorGUILayout.GetControlRect(false, 18); Rect rect = EditorGUILayout.GetControlRect(false, 18);
if (rect.Contains(Event.current.mousePosition)) EditorGUI.DrawRect(rect, new Color(1f, 1f, 1f, 0.1f)); if (rect.Contains(Event.current.mousePosition)) EditorGUI.DrawRect(rect, new Color(1f, 1f, 1f, 0.1f));
GUI.Label(rect, $"[{status}] {path}");
GUIStyle labelStyle = new GUIStyle(EditorStyles.label); if (Event.current.type == EventType.MouseDown && rect.Contains(Event.current.mousePosition)) {
GUI.Label(rect, $"[{status.Trim()}] {path}", labelStyle); if (Event.current.clickCount == 1) PingAsset(path);
else if (Event.current.clickCount == 2) GitDiffViewer.ShowWindow(path, status);
Event e = Event.current; Event.current.Use();
if (e.isMouse && e.button == 0 && rect.Contains(e.mousePosition) && e.type == EventType.MouseDown)
{
if (e.clickCount == 1) PingAsset(path);
else if (e.clickCount == 2) GitDiffViewer.ShowWindow(path, status);
e.Use();
} }
} }
} }
private void OpenCommitInBrowser(string hash) private void OpenCommitInBrowser(string hash)
{ {
string remoteUrl = RunGitCommand("config --get remote.origin.url").Trim(); if (!string.IsNullOrWhiteSpace(webUrlOverride))
if (string.IsNullOrEmpty(remoteUrl))
{ {
UnityEngine.Debug.LogWarning("Git-Tool: No remote repository configured (origin missing)."); string url = webUrlOverride;
if (url.EndsWith("/")) url = url.Substring(0, url.Length - 1);
if (url.EndsWith(".git")) url = url.Substring(0, url.Length - 4);
Application.OpenURL($"{url}/commit/{hash}");
return; return;
} }
string webUrl = remoteUrl; string remoteUrl = RunGitCommand("config --get remote.origin.url").Trim();
if (string.IsNullOrEmpty(remoteUrl)) return;
if (webUrl.StartsWith("git@"))
{
webUrl = webUrl.Replace(":", "/").Replace("git@", "https://");
}
if (webUrl.EndsWith(".git"))
{
webUrl = webUrl.Substring(0, webUrl.Length - 4);
}
Application.OpenURL($"{webUrl}/commit/{hash}"); if (remoteUrl.StartsWith("git@") || remoteUrl.StartsWith("ssh://")) {
remoteUrl = remoteUrl.Replace("ssh://", "");
remoteUrl = remoteUrl.Replace("git@", "https://");
int firstColon = remoteUrl.IndexOf(':', 8);
if (firstColon != -1) remoteUrl = remoteUrl.Remove(firstColon, 1).Insert(firstColon, "/");
}
if (remoteUrl.EndsWith(".git")) remoteUrl = remoteUrl.Substring(0, remoteUrl.Length - 4);
Application.OpenURL($"{remoteUrl}/commit/{hash}");
} }
private void CheckRepoStatus() private void CheckRepoStatus()
{ {
string projectPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); string projectPath = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
hasRepo = Directory.Exists(Path.Combine(projectPath, ".git")); hasRepo = Directory.Exists(Path.Combine(projectPath, ".git"));
if (hasRepo) {
if (hasRepo)
{
string output = RunGitCommand("status -s"); string output = RunGitCommand("status -s");
changedFiles = string.IsNullOrWhiteSpace(output) ? new string[0] : output.Split(new[] { '\n', '\r' }, System.StringSplitOptions.RemoveEmptyEntries); changedFiles = string.IsNullOrWhiteSpace(output) ? new string[0] : output.Split(new[] { '\n', '\r' }, System.StringSplitOptions.RemoveEmptyEntries);
FetchHistory(); FetchHistory();
} }
} }
private void FetchBranches()
{
string output = RunGitCommand("branch --format=\"%(refname:short)\"");
if (!string.IsNullOrWhiteSpace(output))
{
availableBranches = output.Split(new[] { '\n', '\r' }, System.StringSplitOptions.RemoveEmptyEntries);
selectedBranchIndex = System.Array.IndexOf(availableBranches, currentBranchName);
if (selectedBranchIndex == -1) selectedBranchIndex = 0;
}
}
private void FetchHistory() private void FetchHistory()
{ {
commitHistory.Clear(); commitHistory.Clear();
string output = RunGitCommand("log -n 25 --pretty=format:\"%h|%cd|%s\" --date=short"); string output = RunGitCommand("log -n 25 --pretty=format:\"%h|%cd|%s\" --date=short");
if (!string.IsNullOrWhiteSpace(output)) if (!string.IsNullOrWhiteSpace(output)) {
{ foreach (string line in output.Split('\n')) {
string[] lines = output.Split(new[] { '\n', '\r' }, System.StringSplitOptions.RemoveEmptyEntries); string[] p = line.Split('|');
foreach (string line in lines) if (p.Length >= 3) commitHistory.Add(new CommitInfo { hash = p[0], date = p[1], message = p[2] });
{
string[] parts = line.Split('|');
if (parts.Length >= 3)
{
commitHistory.Add(new CommitInfo { hash = parts[0], date = parts[1], message = parts[2] });
}
} }
} }
} }
private void PingAsset(string relativePath) private void PingAsset(string path) {
{ var obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path);
UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(relativePath); if (obj) { Selection.activeObject = obj; EditorGUIUtility.PingObject(obj); }
if (obj != null) { Selection.activeObject = obj; EditorGUIUtility.PingObject(obj); }
} }
private void GenerateUnityGitIgnore() private void GenerateUnityGitIgnore() {
{
string path = Path.Combine(Application.dataPath, "../.gitignore"); string path = Path.Combine(Application.dataPath, "../.gitignore");
if (!File.Exists(path)) if (!File.Exists(path)) File.WriteAllText(path, ".idea\n.vs\nbin\nobj\n/Library\n/Temp\n/UserSettings\n/Configs\n/*.csproj\n/*.sln\n/Logs\n/Packages/*\n!/Packages/manifest.json\n!/Packages/packages-lock.json\n~UnityDirMonSyncFile~*");
{
string content = @".idea
.vs
bin
obj
*.sln.DotSettings.user
/Library
/Temp
/UserSettings
/Configs
/*.csproj
/*.sln
/Logs
/Packages/*
!/Packages/manifest.json
!/Packages/packages-lock.json
!/Packages/vpm-manifest.json
~UnityDirMonSyncFile~*";
File.WriteAllText(path, content);
UnityEngine.Debug.Log(".gitignore generated successfully!");
}
} }
public static string RunGitCommand(string arguments) // FIX: Methode ist wieder static!
{ public static string RunGitCommand(string args, bool logAction = false) {
try try {
{ ProcessStartInfo si = new ProcessStartInfo("git", args) {
ProcessStartInfo startInfo = new ProcessStartInfo("git", arguments) WorkingDirectory = Path.GetFullPath(Path.Combine(Application.dataPath, "..")),
{ UseShellExecute = false,
WorkingDirectory = Path.GetFullPath(Path.Combine(Application.dataPath, "..")), RedirectStandardOutput = true,
UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true RedirectStandardError = true,
CreateNoWindow = true
}; };
using (Process p = Process.Start(startInfo)) { p.WaitForExit(); return p.StandardOutput.ReadToEnd(); }
} using (Process p = Process.Start(si)) {
catch { return ""; } string o = p.StandardOutput.ReadToEnd();
string e = p.StandardError.ReadToEnd();
p.WaitForExit();
string result = o + (string.IsNullOrWhiteSpace(e) ? "" : "\n" + e);
if (logAction) {
string time = System.DateTime.Now.ToString("HH:mm:ss");
string entry = $"[{time}] > git {args}\n";
if (!string.IsNullOrWhiteSpace(result)) entry += result.Trim() + "\n\n";
gitLogOutput = entry + gitLogOutput;
if (gitLogOutput.Length > 10000) gitLogOutput = gitLogOutput.Substring(0, 10000);
}
return result;
}
} catch { return ""; }
}
}
public class GitSaveListener : UnityEditor.AssetModificationProcessor
{
public static string[] OnWillSaveAssets(string[] paths) {
EditorApplication.delayCall += () => { if (EditorWindow.HasOpenInstances<GitPanel>()) EditorWindow.GetWindow<GitPanel>("GIT Version Control System").RefreshData(); };
return paths;
} }
} }
@@ -0,0 +1,16 @@
{
"name": "de.mrunknownde.gittool.Editor",
"rootNamespace": "",
"references": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
+62 -26
View File
@@ -1,37 +1,73 @@
# 🛠️ Unity Git Control Tool (VRChat Ready) # 🛠️ MrUnknownDE VRChat Unity Tools
A lightweight, integrated Source Control Panel built directly into Unity. Designed to eliminate the constant context-switching between the Unity Editor and external command-line tools. Perfectly tailored for VRChat World Creators and developers who want to maintain clean version control without the bloat. Welcome to the **MrUnknownDE VRChat Unity Tools** repository. This is a collection of customized, lightweight, and high-performance Unity Editor tools designed specifically to improve the workflow of VRChat World Creators.
## ✨ Features Instead of dealing with standalone applications or command-line interfaces, these tools bring essential DevOps and social features directly into your Unity Editor.
- **One-Click Init:** Initializes a new repository and automatically generates a clean Unity `.gitignore` file.
---
## 📦 Current Tools in this Package
### 1. Git Version Control System
A fully integrated Source Control Panel built directly into Unity. No more context-switching between the editor and external Git clients.
- **Smart Initialization:** Enter a Gitea/GitHub remote URL, and the tool will automatically handle the `init`, branch setup, and pull/merge existing server data before pushing your local project.
- **VS Code Style Interface:** Compact overview of modified, added, deleted, and untracked files. - **VS Code Style Interface:** Compact overview of modified, added, deleted, and untracked files.
- **Auto-Timestamp Commits:** If you don't provide a custom commit message, the tool gracefully falls back to a clean timestamp format. - **Auto-Save Hook:** Pressing `CTRL+S` in Unity or changing focus automatically refreshes the Git status.
- **Interactive File Explorer:** - `Single Click` on a file -> Pings and focuses the asset in the Unity Project View. - **Timestamp Commits:** If you don't provide a custom commit message, the tool gracefully falls back to a clean timestamp format.
- `Double Click` on a file -> Opens the built-in Code Diff Viewer right inside the Editor. - **Interactive File Explorer:** Double-click any file to open the built-in Code Diff Viewer right inside the Editor.
- **History View:** Browse your latest commits. Click any commit to open it directly in your remote web view (Gitea, GitHub, GitLab). - **Revert (Panic Button):** Easily discard all uncommitted changes if an experiment goes wrong.
## 🚀 Installation via VRChat Creator Companion (VCC) ### 2. Discord Rich Presence (RPC)
Let your community know what you are working on without saying a word.
- **Live Status:** Shows your current active Unity scene directly on your Discord profile.
- **Privacy Mode:** Hide the scene name if you are working on an unannounced or secret project.
- **Custom Status:** Add custom text (e.g., "Baking Lightmaps..." or "Writing Udon Scripts") to your Discord activity.
You can add this tool as a custom package directly into your VCC. ---
1. Open the VRChat Creator Companion. ## ⚖️ ⚠️ Achtung: Law & Order (The German Way)
2. Navigate to **Settings** -> **Packages**.
3. Click on **Add Repository**.
4. Enter your custom repo URL: `[YOUR_INDEX_JSON_URL_HERE]`
5. In your project views, under "Manage Project", the **VRChat Git Control Tool** will now appear. Simply click the plus icon to add it.
## 🛠️ Manual Installation Now it's getting serious—or as we say in Germany: **"Jetzt wird es deutsch!"** 🇩🇪
1. Download the latest version as a `.zip` archive.
2. Extract the folder. ### The "Assets Folder" Rule
3. Place the folder directly into your Unity project's `Packages` directory. Listen up, because the German Copyright Law (*Urheberrechtsgesetz*) doesn't take jokes lightly.
*Alternative:* Copy the `.cs` files from the `Editor` folder into any `Editor` folder inside your `Assets` directory.
**DO NOT upload your paid Assets, Store-bought Plugins, or copyrighted Prefabs into a PUBLIC Repository.** If you distribute copyrighted material from creators without permission:
- **Civil Law:** You could face fines (Schadensersatz) that will make your bank account cry.
- **Criminal Law:** According to § 106 UrhG, unauthorized exploitation of copyrighted works can lead to **up to 3 years in prison** or heavy fines.
**Pro-Tip:** Always use a **Private Repository** (e.g., on a private Gitea server or a private GitHub repo) if your project contains paid assets. Your wallet and your freedom will thank you. Don't let the "Abmahnanwalt" be your first beta tester ;)
---
## 🚀 Installation
This tool is installed manually directly into your Unity project.
### Direct Folder Drop
1. Download the latest `mrunknownde-vcc-tools-v20xx-xx-xx.zip` release.
2. Extract the archive.
3. Drag and drop the extracted folder directly into the `Assets` directory inside your Unity project's root folder (using Windows Explorer / File Explorer, not inside the Unity Editor window). Unity will automatically compile the tools.
---
## 🕹️ Usage ## 🕹️ Usage
Once installed, open the tool via the top menu bar in Unity:
`Tools` -> `Git-Tool`
A floating window will appear. You can easily dock this window into your custom layout (e.g., right next to the Inspector). Once installed, you can access the tools via the top menu bar in Unity:
## ⚠️ Prerequisites `Tools` -> `MrUnknownDE` -> `GIT Version Control`
- **Git** must be installed on your system and added to your global environment variables (`PATH`). `Tools` -> `MrUnknownDE` -> `Discord RPC`
- For automatic pushes to Gitea/GitHub to work seamlessly, you should have **SSH keys** or cached credentials configured. Unity cannot intercept terminal password prompts.
The tools will open as floating windows. You can easily dock them into your custom Unity layout (e.g., right next to the Inspector or Console).
---
## ⚠️ Prerequisites & Troubleshooting
- **Git Installation:** You must have Git installed on your Windows machine. If the tool does not detect Git, it will provide a download link within the Unity UI.
- **Environment Variables:** If you just installed Git, **you must completely restart Unity Hub and the Unity Editor** so Windows can load the new `PATH` variables.
- **Authentication:** For automatic pushes to remote servers (Gitea/GitHub) to work seamlessly, ensure you have SSH keys or cached Git credentials configured on your system. Unity cannot intercept terminal password prompts.
---
*Built with ❤️ for the VRChat Community.*
-12
View File
@@ -1,12 +0,0 @@
{
"name": "de.mrunknownde.gittool",
"version": "1.0.0",
"displayName": "VRChat Git Control Tool",
"description": "A lightweight, integrated Git panel for Unity. Ideal for VRChat World/Avatar Creators to easily push commits directly from the editor to Gitea/GitHub/GitLab and other Git hosting services.",
"unity": "2022.3",
"author": {
"name": "mrunknownde"
},
"dependencies": {},
"vpmDependencies": {}
}