add git web url override

This commit is contained in:
2026-04-07 11:14:32 +02:00
parent 0955383fb2
commit e247d4ea4d

View File

@@ -8,8 +8,15 @@ public class GitPanel : EditorWindow
{
private string commitMessage = "";
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;
@@ -17,6 +24,11 @@ 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>();
@@ -24,28 +36,34 @@ public class GitPanel : EditorWindow
public static void ShowWindow()
{
GitPanel window = GetWindow<GitPanel>("GIT Version Control System");
window.minSize = new Vector2(350, 500);
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();
}
@@ -55,47 +73,40 @@ public class GitPanel : EditorWindow
private void CheckGitInstallation()
{
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;
}
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 SetDefaultCommitMessage() { commitMessage = $"Auto-Save: {System.DateTime.Now:yyyy-MM-dd HH:mm:ss}"; }
private void OnGUI()
{
GUILayout.Space(10);
GUILayout.Label("GIT Version Control System", EditorStyles.boldLabel);
if (hasRepo) GUILayout.Label($"Active Branch: {currentBranchName}", EditorStyles.miniLabel);
GUILayout.Space(5);
if (!isGitInstalled)
{
RenderGitMissingUI();
return;
}
if (!isGitInstalled) { RenderGitMissingUI(); return; }
if (!hasRepo) { RenderInitUI(); return; }
if (!hasRepo)
// --- 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));
@@ -107,357 +118,225 @@ public class GitPanel : EditorWindow
private void RenderGitMissingUI()
{
EditorGUILayout.HelpBox("CRITICAL: Git is not installed or not found in your system's PATH environment variable. This tool requires Git to function.", MessageType.Error);
GUILayout.Space(15);
GUI.backgroundColor = new Color(0.8f, 0.3f, 0.3f);
if (GUILayout.Button("Download Git for Windows", GUILayout.Height(40)))
{
Application.OpenURL("https://git-scm.com/download/win");
}
GUI.backgroundColor = Color.white;
GUILayout.Space(15);
EditorGUILayout.HelpBox("⚠️ IMPORTANT:\nAfter installing Git, you must completely restart the Unity Hub and the Unity Editor so Windows can load the new PATH variables.", MessageType.Warning);
GUILayout.Space(15);
if (GUILayout.Button("↻ Check Again", GUILayout.Height(30)))
{
RefreshData();
}
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)))
{
bool doMerge = false;
bool doForce = false;
// --- NEU: Intelligenter Remote-Check VOR der Initialisierung ---
if (!string.IsNullOrWhiteSpace(remoteUrlInput))
{
string url = remoteUrlInput.Trim();
// Pingt den Server an, um zu schauen, ob dort schon Commits/Branches existieren
string remoteStatus = RunGitCommand($"ls-remote \"{url}\"");
if (!string.IsNullOrWhiteSpace(remoteStatus) && (remoteStatus.Contains("HEAD") || remoteStatus.Contains("refs/heads/")))
{
// Der Server hat bereits Daten! Wir stoppen und fragen den User.
int choice = EditorUtility.DisplayDialogComplex(
"Remote Repository Not Empty!",
"The provided remote URL already contains data (e.g. a README, License, or old commits).\n\n" +
"Merge: Combines remote data with your local project safely.\n" +
"Overwrite: FORCES your local state onto the server (DELETES ALL remote data!).",
"Merge (Safe)",
"Cancel",
"Overwrite (Destructive)"
);
if (choice == 1) // Cancel
{
UnityEngine.Debug.Log("Git-Tool: Initialization aborted by user to prevent data loss.");
return; // Bricht komplett ab, es wird kein lokales Repo erstellt
}
else if (choice == 0) // Merge
{
doMerge = true;
}
else if (choice == 2) // Overwrite
{
doForce = true;
}
}
}
// --- Ab hier läuft der normale Init-Prozess ---
RunGitCommand("init");
RunGitCommand("branch -M main");
if (!string.IsNullOrWhiteSpace(remoteUrlInput))
{
if (!string.IsNullOrWhiteSpace(remoteUrlInput)) {
RunGitCommand($"remote add origin \"{remoteUrlInput.Trim()}\"");
if (doMerge)
{
// Zieht die Server-Daten sanft rein
RunGitCommand("pull origin main --allow-unrelated-histories --no-edit");
}
RunGitCommand("pull origin main --allow-unrelated-histories --no-edit");
}
GenerateUnityGitIgnore();
AssetDatabase.Refresh();
RunGitCommand("add .gitignore");
RunGitCommand("commit -m \"Initial commit (GitIgnore)\"");
if (!string.IsNullOrWhiteSpace(remoteUrlInput))
{
if (doForce)
{
// Brutaler Force Push (Löscht alte Daten auf dem Gitea)
RunGitCommand("push -u origin main --force");
UnityEngine.Debug.LogWarning("Git-Tool: Remote repository OVERWRITTEN with local state!");
}
else
{
// Normaler Push (funktioniert, da wir vorher gemerged haben, falls nötig)
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!");
RefreshData();
}
GUI.backgroundColor = new Color(0.8f, 0.3f, 0.3f);
if (GUILayout.Button("⎌ Revert All", GUILayout.Width(100), GUILayout.Height(30)))
if (GUILayout.Button("⎌ Revert All", GUILayout.Width(80), GUILayout.Height(30)))
{
if (EditorUtility.DisplayDialog("Revert All Changes?",
"Are you absolutely sure you want to discard ALL uncommitted changes?\n\nThis will reset tracked files and delete newly created untracked files. This action CANNOT be undone!",
"Yes, Nuke it!", "Cancel"))
{
RunGitCommand("reset --hard HEAD");
RunGitCommand("clean -fd");
RefreshData();
UnityEngine.Debug.LogWarning("Git-Tool: All uncommitted changes have been destroyed.");
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.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();
}
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>("GIT Version Control System").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;
}
}