4 Commits

Author SHA1 Message Date
e247d4ea4d add git web url override 2026-04-07 11:14:32 +02:00
0955383fb2 add the german law 2026-04-07 11:07:36 +02:00
8450f0ccbb Update Readme.md 2026-04-07 10:58:50 +02:00
99e6d462cf add init check and revert button 2026-04-07 10:58:07 +02:00
2 changed files with 265 additions and 207 deletions

View File

@@ -7,8 +7,16 @@ using System.Collections.Generic;
public class GitPanel : EditorWindow
{
private string commitMessage = "";
private string remoteUrlInput = ""; // NEU: Speicher für die Remote-URL
private string remoteUrlInput = "";
private string newBranchName = "";
private bool isGitInstalled = true;
private bool hasRepo = false;
private string currentBranchName = "unknown";
private string[] availableBranches = new string[0];
private int selectedBranchIndex = 0;
private string[] changedFiles = new string[0];
private Vector2 scrollPositionChanges;
private Vector2 scrollPositionHistory;
@@ -16,31 +24,46 @@ public class GitPanel : EditorWindow
private int selectedTab = 0;
private string[] tabNames = { "Changes", "History" };
// NEU: Settings & Override
private bool showSettings = false;
private string webUrlOverride = "";
private string prefsKey = "";
private struct CommitInfo { public string hash; public string date; public string message; }
private List<CommitInfo> commitHistory = new List<CommitInfo>();
[MenuItem("Tools/MrUnknownDE/Git-Tool")]
[MenuItem("Tools/MrUnknownDE/GIT Version Control")]
public static void ShowWindow()
{
GitPanel window = GetWindow<GitPanel>("Source Control");
window.minSize = new Vector2(350, 500);
GitPanel window = GetWindow<GitPanel>("GIT Version Control System");
window.minSize = new Vector2(350, 550);
}
private void OnEnable()
{
RefreshData();
}
private void OnFocus()
{
RefreshData();
private void OnEnable()
{
// Generiert einen einzigartigen Key für dieses spezifische Unity-Projekt
prefsKey = $"GitTool_WebUrl_{Application.dataPath.GetHashCode()}";
webUrlOverride = EditorPrefs.GetString(prefsKey, "");
RefreshData();
}
private void OnFocus() { RefreshData(); }
public void RefreshData()
{
CheckGitInstallation();
if (!isGitInstalled) return;
CheckRepoStatus();
if (string.IsNullOrWhiteSpace(commitMessage) || commitMessage.StartsWith("Auto-Save:"))
if (hasRepo)
{
currentBranchName = RunGitCommand("rev-parse --abbrev-ref HEAD").Trim();
FetchBranches();
}
if (string.IsNullOrWhiteSpace(commitMessage) || commitMessage.StartsWith("Auto-Save:"))
{
SetDefaultCommitMessage();
}
@@ -48,21 +71,42 @@ public class GitPanel : EditorWindow
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; }
}
private void SetDefaultCommitMessage() { commitMessage = $"Auto-Save: {System.DateTime.Now:yyyy-MM-dd HH:mm:ss}"; }
private void OnGUI()
{
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);
if (!hasRepo)
if (!isGitInstalled) { RenderGitMissingUI(); return; }
if (!hasRepo) { RenderInitUI(); return; }
// --- NEU: SETTINGS FOLDOUT ---
showSettings = EditorGUILayout.Foldout(showSettings, "⚙️ Repository Settings");
if (showSettings)
{
RenderInitUI();
return;
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.HelpBox("e.g. https://git.mrunk.de/mrunknownde/my-repo\nLeaves SSH untouched but fixes browser links.", MessageType.None);
EditorGUILayout.EndVertical();
GUILayout.Space(5);
}
selectedTab = GUILayout.Toolbar(selectedTab, tabNames, GUILayout.Height(25));
@@ -72,268 +116,227 @@ public class GitPanel : EditorWindow
else RenderHistoryUI();
}
private void RenderGitMissingUI()
{
EditorGUILayout.HelpBox("CRITICAL: Git not found. Please install Git and restart Unity.", MessageType.Error);
if (GUILayout.Button("Download Git for Windows", GUILayout.Height(30))) Application.OpenURL("https://git-scm.com/download/win");
}
private void RenderInitUI()
{
EditorGUILayout.HelpBox("No local Git repository found. Initialize current project folder?", MessageType.Warning);
GUILayout.Space(10);
GUILayout.Label("Remote Repository URL (Optional):", EditorStyles.boldLabel);
remoteUrlInput = EditorGUILayout.TextField(remoteUrlInput, GUILayout.Height(25));
EditorGUILayout.HelpBox("e.g., https://github.com/user/repo.git or git@gitea.domain.com:user/repo.git", MessageType.Info);
GUILayout.Space(10);
EditorGUILayout.HelpBox("No local Git repository found.", MessageType.Warning);
remoteUrlInput = EditorGUILayout.TextField("Remote URL:", remoteUrlInput);
if (GUILayout.Button("Initialize Repository", GUILayout.Height(30)))
{
RunGitCommand("init");
// Branch direkt sauber auf "main" setzen (verhindert Konflikte mit alten "master" Defaults)
RunGitCommand("branch -M main");
// Remote URL hinzufügen, falls angegeben
if (!string.IsNullOrWhiteSpace(remoteUrlInput))
{
if (!string.IsNullOrWhiteSpace(remoteUrlInput)) {
RunGitCommand($"remote add origin \"{remoteUrlInput.Trim()}\"");
RunGitCommand("pull origin main --allow-unrelated-histories --no-edit");
}
GenerateUnityGitIgnore();
AssetDatabase.Refresh();
RunGitCommand("add .gitignore");
RunGitCommand("commit -m \"Initial commit (GitIgnore)\"");
// Initialen Push ausführen, wenn ein Remote existiert
if (!string.IsNullOrWhiteSpace(remoteUrlInput))
{
RunGitCommand("push -u origin main");
UnityEngine.Debug.Log("Git-Tool: Repository initialized and pushed to remote!");
}
else
{
UnityEngine.Debug.Log("Git-Tool: Local repository initialized successfully.");
}
if (!string.IsNullOrWhiteSpace(remoteUrlInput)) RunGitCommand("push -u origin main");
RefreshData();
}
}
private void RenderGitUI()
{
// --- BRANCH MANAGEMENT ---
EditorGUILayout.BeginVertical("box");
GUILayout.Label("Branch Management", EditorStyles.boldLabel);
if (availableBranches.Length > 0)
{
EditorGUI.BeginChangeCheck();
int newIndex = EditorGUILayout.Popup("Switch Branch:", selectedBranchIndex, availableBranches);
if (EditorGUI.EndChangeCheck() && newIndex != selectedBranchIndex)
{
RunGitCommand($"checkout \"{availableBranches[newIndex]}\"");
RefreshData();
return;
}
}
EditorGUILayout.BeginHorizontal();
newBranchName = EditorGUILayout.TextField("New Branch:", newBranchName);
GUI.backgroundColor = new Color(0.2f, 0.6f, 0.2f);
if (GUILayout.Button("+ Create", GUILayout.Width(80)))
{
if (!string.IsNullOrWhiteSpace(newBranchName))
{
RunGitCommand($"checkout -b \"{newBranchName.Trim()}\"");
newBranchName = "";
RefreshData();
GUI.FocusControl(null);
return;
}
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
GUILayout.Space(10);
// --- COMMIT BEREICH ---
commitMessage = EditorGUILayout.TextField(commitMessage, GUILayout.Height(25));
EditorGUILayout.BeginHorizontal();
GUI.backgroundColor = new Color(0.2f, 0.4f, 0.8f);
if (GUILayout.Button("✓ Commit & Push", GUILayout.Height(30)))
{
if (string.IsNullOrWhiteSpace(commitMessage)) commitMessage = $"Auto-Save: {System.DateTime.Now:yyyy-MM-dd HH:mm:ss}";
if (string.IsNullOrWhiteSpace(commitMessage)) SetDefaultCommitMessage();
RunGitCommand("add .");
RunGitCommand($"commit -m \"{commitMessage}\"");
RunGitCommand("push");
RunGitCommand("push -u origin HEAD");
commitMessage = "";
RefreshData();
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();
GUILayout.Label($"CHANGES ({changedFiles.Length})", EditorStyles.boldLabel);
if (GUILayout.Button("↻", GUILayout.Width(25)))
{
RefreshData();
}
GUI.backgroundColor = new Color(0.8f, 0.3f, 0.3f);
if (GUILayout.Button("⎌ Revert All", GUILayout.Width(80), GUILayout.Height(30)))
{
if (EditorUtility.DisplayDialog("Revert Changes?", "Discard ALL uncommitted changes?", "Yes", "Cancel")) {
RunGitCommand("reset --hard HEAD"); RunGitCommand("clean -fd"); 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();
scrollPositionChanges = EditorGUILayout.BeginScrollView(scrollPositionChanges, "box");
if (changedFiles.Length == 0) GUILayout.Label("No unsaved changes.");
if (changedFiles.Length == 0) GUILayout.Label("No changes.");
else RenderFileList(changedFiles);
EditorGUILayout.EndScrollView();
}
private void RenderHistoryUI()
{
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();
EditorGUILayout.EndHorizontal();
GUILayout.Space(5);
if (commitHistory.Count == 0)
{
GUILayout.Label("No commits found.");
return;
}
scrollPositionHistory = EditorGUILayout.BeginScrollView(scrollPositionHistory, "box");
foreach (var commit in commitHistory)
{
foreach (var commit in commitHistory) {
Rect rect = EditorGUILayout.GetControlRect(false, 22);
if (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))
{
if (rect.Contains(Event.current.mousePosition)) EditorGUI.DrawRect(rect, new Color(1f, 1f, 1f, 0.1f));
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)) {
OpenCommitInBrowser(commit.hash);
e.Use();
Event.current.Use();
}
GUILayout.Space(2);
}
EditorGUILayout.EndScrollView();
}
private void RenderFileList(string[] files)
{
foreach (string line in files)
{
foreach (string line in files) {
if (line.Length < 4) continue;
string status = line.Substring(0, 1).Trim() == "" ? line.Substring(0, 2) : line.Substring(0, 1);
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);
string status = line.Substring(0, 2).Trim();
string path = line.Substring(3).Trim().Replace("\"", "");
Rect rect = EditorGUILayout.GetControlRect(false, 18);
if (rect.Contains(Event.current.mousePosition)) EditorGUI.DrawRect(rect, new Color(1f, 1f, 1f, 0.1f));
GUIStyle labelStyle = new GUIStyle(EditorStyles.label);
GUI.Label(rect, $"[{status.Trim()}] {path}", labelStyle);
Event e = Event.current;
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();
GUI.Label(rect, $"[{status}] {path}");
if (Event.current.type == EventType.MouseDown && rect.Contains(Event.current.mousePosition)) {
if (Event.current.clickCount == 1) PingAsset(path);
else if (Event.current.clickCount == 2) GitDiffViewer.ShowWindow(path, status);
Event.current.Use();
}
}
}
private void OpenCommitInBrowser(string hash)
{
string remoteUrl = RunGitCommand("config --get remote.origin.url").Trim();
if (string.IsNullOrEmpty(remoteUrl))
// NEU: Override Logik greift zuerst!
if (!string.IsNullOrWhiteSpace(webUrlOverride))
{
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;
}
string webUrl = remoteUrl;
if (webUrl.StartsWith("git@"))
{
webUrl = webUrl.Replace(":", "/").Replace("git@", "https://");
}
if (webUrl.EndsWith(".git"))
{
webUrl = webUrl.Substring(0, webUrl.Length - 4);
}
// Standard Fallback Logik (wenn kein Override gesetzt ist)
string remoteUrl = RunGitCommand("config --get remote.origin.url").Trim();
if (string.IsNullOrEmpty(remoteUrl)) return;
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()
{
string projectPath = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
hasRepo = Directory.Exists(Path.Combine(projectPath, ".git"));
if (hasRepo)
{
if (hasRepo) {
string output = RunGitCommand("status -s");
changedFiles = string.IsNullOrWhiteSpace(output) ? new string[0] : output.Split(new[] { '\n', '\r' }, System.StringSplitOptions.RemoveEmptyEntries);
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()
{
commitHistory.Clear();
string output = RunGitCommand("log -n 25 --pretty=format:\"%h|%cd|%s\" --date=short");
if (!string.IsNullOrWhiteSpace(output))
{
string[] lines = output.Split(new[] { '\n', '\r' }, System.StringSplitOptions.RemoveEmptyEntries);
foreach (string line in lines)
{
string[] parts = line.Split('|');
if (parts.Length >= 3)
{
commitHistory.Add(new CommitInfo { hash = parts[0], date = parts[1], message = parts[2] });
}
if (!string.IsNullOrWhiteSpace(output)) {
foreach (string line in output.Split('\n')) {
string[] p = line.Split('|');
if (p.Length >= 3) commitHistory.Add(new CommitInfo { hash = p[0], date = p[1], message = p[2] });
}
}
}
private void PingAsset(string relativePath)
{
UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(relativePath);
if (obj != null) { Selection.activeObject = obj; EditorGUIUtility.PingObject(obj); }
private void PingAsset(string path) {
var obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path);
if (obj) { Selection.activeObject = obj; EditorGUIUtility.PingObject(obj); }
}
private void GenerateUnityGitIgnore()
{
private void GenerateUnityGitIgnore() {
string path = Path.Combine(Application.dataPath, "../.gitignore");
if (!File.Exists(path))
{
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!");
}
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~*");
}
public static string RunGitCommand(string arguments)
{
try
{
ProcessStartInfo startInfo = new ProcessStartInfo("git", arguments)
{
WorkingDirectory = Path.GetFullPath(Path.Combine(Application.dataPath, "..")),
UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true
};
using (Process p = Process.Start(startInfo)) { p.WaitForExit(); return p.StandardOutput.ReadToEnd(); }
}
catch { return ""; }
public static string RunGitCommand(string args) {
try {
ProcessStartInfo si = new ProcessStartInfo("git", args) { WorkingDirectory = Path.GetFullPath(Path.Combine(Application.dataPath, "..")), UseShellExecute = false, RedirectStandardOutput = true, CreateNoWindow = true };
using (Process p = Process.Start(si)) { string o = p.StandardOutput.ReadToEnd(); p.WaitForExit(); return o; }
} catch { return ""; }
}
}
public class GitSaveListener : UnityEditor.AssetModificationProcessor
{
public static string[] OnWillSaveAssets(string[] paths)
{
EditorApplication.delayCall += () =>
{
if (EditorWindow.HasOpenInstances<GitPanel>())
{
EditorWindow.GetWindow<GitPanel>().RefreshData();
}
};
return paths;
public static string[] OnWillSaveAssets(string[] paths) {
EditorApplication.delayCall += () => { if (EditorWindow.HasOpenInstances<GitPanel>()) EditorWindow.GetWindow<GitPanel>("GIT Version Control System").RefreshData(); };
return paths;
}
}

View File

@@ -1,27 +1,82 @@
# 🛠️ 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
- **One-Click Init:** Initializes a new repository and automatically generates a clean Unity `.gitignore` file.
Instead of dealing with standalone applications or command-line interfaces, these tools bring essential DevOps and social features directly into your Unity Editor.
---
## 📦 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.
- **Auto-Timestamp Commits:** If you don't provide a custom commit message, the tool gracefully falls back to a clean timestamp format.
- **Interactive File Explorer:** - `Single Click` on a file -> Pings and focuses the asset in the Unity Project View.
- `Double Click` on a file -> Opens 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).
- **Auto-Save Hook:** Pressing `CTRL+S` in Unity or changing focus automatically refreshes the Git status.
- **Timestamp Commits:** If you don't provide a custom commit message, the tool gracefully falls back to a clean timestamp format.
- **Interactive File Explorer:** Double-click any file to open the built-in Code Diff Viewer right inside the Editor.
- **Revert (Panic Button):** Easily discard all uncommitted changes if an experiment goes wrong.
## 🛠️ Manual Installation
1. Download the latest version as a `.zip` archive.
2. Extract the folder.
3. Place the folder directly into your Unity project's `Packages` directory.
*Alternative:* Copy the `.cs` files from the `Editor` folder into any `Editor` folder inside your `Assets` directory.
### 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.
---
## ⚖️ ⚠️ Achtung: Law & Order (The German Way)
Now it's getting serious—or as we say in Germany: **"Jetzt wird es deutsch!"** 🇩🇪
### The "Assets Folder" Rule
Listen up, because the German Copyright Law (*Urheberrechtsgesetz*) doesn't take jokes lightly.
**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.
### Method 1: Unity Package Manager (Recommended)
1. Go to the [Releases page](../../releases/latest) of this repository.
2. Download the latest `de.mrunknownde.gittool-vX.X.X.zip` file.
3. Extract the ZIP file into a folder on your PC.
4. Open your Unity Project.
5. Go to `Window` -> `Package Manager`.
6. Click the **+** icon in the top left corner and select **Add package from disk...**.
7. Navigate to the extracted folder, select the `package.json` file, and click Open.
### Method 2: Direct Folder Drop
1. Download the latest `.zip` release.
2. Extract the archive.
3. Drag and drop the extracted folder directly into the `Packages` 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
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
- **Git** must be installed on your system and added to your global environment variables (`PATH`).
- For automatic pushes to Gitea/GitHub to work seamlessly, you should have **SSH keys** or cached credentials configured. Unity cannot intercept terminal password prompts.
`Tools` -> `MrUnknownDE` -> `GIT Version Control`
`Tools` -> `MrUnknownDE` -> `Discord RPC`
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.*