Skip to content

Instantly share code, notes, and snippets.

@strich
Created December 18, 2019 14:58
Show Gist options
  • Save strich/7566f47e5d2ca7d7dc33abff3be1b8bf to your computer and use it in GitHub Desktop.
Save strich/7566f47e5d2ca7d7dc33abff3be1b8bf to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Brightrock.Library.BehaviourTree;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
namespace Game.Tools.Editor.Assets
{
using Object = UnityEngine.Object;
[InitializeOnLoad]
public class GitLFSFileLock
{
const float LFSLockUpdateCheckInterval = 60f;
const string AssetsSubpath = "/Game/Assets";
const string FilelockTag = "GitLFSLockActive";
public static List<ResourceGraph> LockedFiles = new List<ResourceGraph>();
static int _activeProcesses = 0;
static readonly Texture[] _loadingSpinners = new Texture[]
{
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin00.png"),
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin01.png"),
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin02.png"),
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin03.png"),
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin04.png"),
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin05.png"),
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin06.png"),
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin07.png"),
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin08.png"),
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin09.png"),
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin10.png"),
(Texture)EditorGUIUtility.Load("icons/d_WaitSpin11.png")
};
static readonly Dictionary<string, string> _lockedGraphs = new Dictionary<string, string>();
static readonly Stack<string> _warnCannotLock = new Stack<string>();
static List<string> _allGraphsPaths = new List<string>();
static List<string> AllGraphPaths
{
get
{
if (_allGraphsPaths != null) return _allGraphsPaths;
_allGraphsPaths.Clear();
var paths = AssetDatabase.FindAssets("t:resourcegraph", null).ToList();
return _allGraphsPaths = paths;
}
}
static GitLFSFileLock()
{
EditorApplication.update -= Update;
EditorApplication.update += Update;
}
public static void QueryCurrentLocks()
{
RunGitCommand("git lfs locks --json", QueryCurrentLocksReturn);
}
public static string CurrentUsername { get; private set; }
static bool _gettingCurrentUser;
public static void GetCurrentUser()
{
_gettingCurrentUser = true;
RunGitCommand("git config user.name", (sender, e) =>
{
_activeProcesses--;
CurrentUsername = ( (Process)sender ).StandardOutput.ReadLine();
_gettingCurrentUser = false;
});
}
public static void LockFile(Object obj)
{
var path = AssetDatabase.GetAssetPath(obj);
RunGitCommand($"git lfs lock \"Game/{path}\" --json");
}
public static void UnlockFile(Object obj)
{
var path = AssetDatabase.GetAssetPath(obj);
RunGitCommand($"git lfs unlock \"Game/{path}\" --json", null, "Game/" + path);
}
public static void ForceUnlockFile(Object obj)
{
var path = AssetDatabase.GetAssetPath(obj);
RunGitCommand($"git lfs unlock \"Game/{path}\" --force --json", null, "Game/" + path);
}
public static bool IsAssetLocked(Object obj, out bool lockedLocally)
{
if (!_gettingCurrentUser && string.IsNullOrEmpty(CurrentUsername)) GetCurrentUser();
var path = AssetDatabase.GetAssetPath(obj);
var locked = _lockedGraphs.TryGetValue("Game/" + path, out var locker);
if (locked) lockedLocally = locker.Equals(CurrentUsername);
else lockedLocally = false;
return locked;
}
public static string GetAssetLocker(Object obj)
{
var path = AssetDatabase.GetAssetPath(obj);
_lockedGraphs.TryGetValue("Game/" + path, out var name);
return name;
}
public static bool ProcessRunning()
{
return _activeProcesses > 0;
}
static int _currentSpinnerIndex = -1;
public static Texture GetSpinner()
{
_currentSpinnerIndex++;
if (_currentSpinnerIndex >= _loadingSpinners.Length) _currentSpinnerIndex = 0;
return _loadingSpinners[_currentSpinnerIndex];
}
static bool _queryNextTick;
static void Update()
{
//Some Unity calls cannot be made from Process.Exited
if (_queryNextTick)
{
QueryCurrentLocks();
_queryNextTick = false;
}
if (_warnCannotLock.Count > 0)
{
var warning = _warnCannotLock.Pop();
if (warning != null)
{
EditorUtility.DisplayDialog("Git LFS File Locking Error", $"{warning}", "Continue");
}
}
if (!_gettingCurrentUser && string.IsNullOrEmpty(CurrentUsername)) GetCurrentUser();
}
static void RunGitCommand(string command, EventHandler result = null, string name = "")
{
_activeProcesses++;
var gitPath = Application.dataPath.Replace(AssetsSubpath, "").Replace("/", "\\");
var p = new PathedProcess()
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c " + command,
WorkingDirectory = gitPath,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
},
Path = name,
EnableRaisingEvents = true
};
if (result != null) p.Exited += result;
else p.Exited += GitLockProcess_Exited;
p.Start();
}
static void GitLockProcess_Exited(object sender, EventArgs e)
{
_activeProcesses--;
var process = (PathedProcess)sender;
process.Exited -= GitLockProcess_Exited;
if (process.ExitCode != 0)
{
var error = process.StandardError.ReadLine();
_warnCannotLock.Push($"ExitCode: {process.ExitCode.ToString()} | " + error);
return;
}
var path = process.Path;
if(!string.IsNullOrEmpty(path) && _lockedGraphs.ContainsKey(path)) _lockedGraphs.Remove(path);
_queryNextTick = true;
process.Dispose();
}
static void QueryCurrentLocksReturn(object sender, EventArgs e)
{
_activeProcesses--;
var process = (Process)sender;
process.Exited -= QueryCurrentLocksReturn;
var output = process.StandardOutput.ReadToEnd();
_lockedGraphs.Clear();
dynamic returnObjs = JArray.Parse(output);
foreach (var obj in returnObjs)
{
var nameOutput = (string)obj.owner.name;
var path = (string)obj.path;
var match = Regex.Match(nameOutput, @"\d+");
var userName = "";
if (match.Success)
{
var user = GetUsernameByID(match.Value);
user.Wait();
userName = user.Result;
}
else userName = nameOutput;
_lockedGraphs[path] = userName;
//_lockedGraphs.Add(path, userName);
}
process.Dispose();
}
static async Task<string> GetUsernameByID(string userId)
{
var uri = new Uri($"https://api.github.com/user/{userId}");
using (var hc = new HttpClient())
{
hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
hc.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "http://developer.github.com/v3/#user-agent-required");
var resultTask = await hc.GetStringAsync(uri);
if (!string.IsNullOrEmpty(resultTask))
{
dynamic githubUser = JObject.Parse(resultTask);
return (string)githubUser.name;
}
else
{
return userId;
}
}
}
}
class PathedProcess : Process
{
public string Path;
public PathedProcess() : base() { }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment