├── .docs
└── img
│ ├── context_hierarchy.jpg
│ ├── context_project.jpg
│ ├── lockswindow.PNG
│ ├── preferences.PNG
│ └── username.PNG
├── Editor.meta
├── Editor
├── Scripts.meta
├── Scripts
│ ├── GitLocks.cs
│ ├── GitLocks.cs.meta
│ ├── GitLocksAssetPostprocessor.cs
│ ├── GitLocksAssetPostprocessor.cs.meta
│ ├── GitLocksDisplay.cs
│ ├── GitLocksDisplay.cs.meta
│ ├── GitLocksFileModificationWarning.cs
│ ├── GitLocksFileModificationWarning.cs.meta
│ ├── GitLocksObject.cs
│ ├── GitLocksObject.cs.meta
│ ├── GitLocksPreferences.cs
│ └── GitLocksPreferences.cs.meta
├── Textures.meta
├── Textures
│ ├── greenLock.png
│ ├── greenLock.png.meta
│ ├── greenLock_cb.png
│ ├── greenLock_cb.png.meta
│ ├── mixedLock.png
│ ├── mixedLock.png.meta
│ ├── mixedLock_cb.png
│ ├── mixedLock_cb.png.meta
│ ├── orangeLock.png
│ ├── orangeLock.png.meta
│ ├── orangeLock_cb.png
│ ├── orangeLock_cb.png.meta
│ ├── redLock.png
│ ├── redLock.png.meta
│ ├── redLock_cb.png
│ └── redLock_cb.png.meta
├── tomduchene.unity-git-locks.editor.asmdef
└── tomduchene.unity-git-locks.editor.asmdef.meta
├── LICENSE
├── LICENSE.meta
├── README.md
├── README.md.meta
├── package.json
└── package.json.meta
/.docs/img/context_hierarchy.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TomDuchene/unity-git-locks/80a2d4ad8b45b5b88b3bc77a79f96b5670dd8f18/.docs/img/context_hierarchy.jpg
--------------------------------------------------------------------------------
/.docs/img/context_project.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TomDuchene/unity-git-locks/80a2d4ad8b45b5b88b3bc77a79f96b5670dd8f18/.docs/img/context_project.jpg
--------------------------------------------------------------------------------
/.docs/img/lockswindow.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TomDuchene/unity-git-locks/80a2d4ad8b45b5b88b3bc77a79f96b5670dd8f18/.docs/img/lockswindow.PNG
--------------------------------------------------------------------------------
/.docs/img/preferences.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TomDuchene/unity-git-locks/80a2d4ad8b45b5b88b3bc77a79f96b5670dd8f18/.docs/img/preferences.PNG
--------------------------------------------------------------------------------
/.docs/img/username.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TomDuchene/unity-git-locks/80a2d4ad8b45b5b88b3bc77a79f96b5670dd8f18/.docs/img/username.PNG
--------------------------------------------------------------------------------
/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7f131ace80ee4b14cbb46174be777942
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Editor/Scripts.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: bff6d47978284334e9b86d1481426c18
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Editor/Scripts/GitLocks.cs:
--------------------------------------------------------------------------------
1 | // All rights reserved.
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Diagnostics;
6 | using System.Linq;
7 | using Newtonsoft.Json.Linq;
8 | using UnityEditor;
9 | using UnityEngine;
10 | using UnityEngine.SceneManagement;
11 |
12 | [InitializeOnLoad]
13 | public class GitLocks : ScriptableObject
14 | {
15 | private static List lockedObjectsCache;
16 | private static List uncommitedFilesCache;
17 | private static List modifiedOnServerFilesCache;
18 | private static bool uncommitedFilesCacheDirty = true;
19 | private static List conflictWarningIgnoreList;
20 | private static DateTime lastRefresh = DateTime.MinValue;
21 | private static bool currentlyRefreshing = false;
22 | private static string gitVersion;
23 |
24 | private static float requestTimeout = 30; // seconds
25 |
26 | private static string refreshCallbackResult;
27 | private static string refreshCallbackError;
28 |
29 | // Ignore meta files to check lockable/unlockable files
30 | static string[] ignoredExtensions = new[] { ".meta" };
31 |
32 | static GitLocks()
33 | {
34 | EditorApplication.update += Update;
35 | EditorApplication.wantsToQuit += WantsToQuit;
36 |
37 | // Preferences default
38 | if (!EditorPrefs.HasKey("gitLocksEnabled"))
39 | {
40 | EditorPrefs.SetBool("gitLocksEnabled", true);
41 | }
42 |
43 | if (!EditorPrefs.HasKey("gitLocksAutoRefreshLocks"))
44 | {
45 | EditorPrefs.SetBool("gitLocksAutoRefreshLocks", true);
46 | }
47 |
48 | if (!EditorPrefs.HasKey("gitLocksRefreshLocksInterval"))
49 | {
50 | EditorPrefs.SetInt("gitLocksRefreshLocksInterval", 5);
51 | }
52 |
53 | if (!EditorPrefs.HasKey("gitLocksMaxFilesNumPerRequest"))
54 | {
55 | EditorPrefs.SetInt("gitLocksMaxFilesNumPerRequest", 15);
56 | }
57 |
58 | if (!EditorPrefs.HasKey("displayLocksConflictWarning"))
59 | {
60 | EditorPrefs.SetBool("displayLocksConflictWarning", true);
61 | }
62 |
63 | if (!EditorPrefs.HasKey("warnIfIStillOwnLocksOnQuit"))
64 | {
65 | EditorPrefs.SetBool("warnIfIStillOwnLocksOnQuit", true);
66 | }
67 |
68 | if (!EditorPrefs.HasKey("warnIfFileHasBeenModifiedOnServer"))
69 | {
70 | EditorPrefs.SetBool("warnIfFileHasBeenModifiedOnServer", true);
71 | }
72 |
73 | if (!EditorPrefs.HasKey("notifyNewLocks"))
74 | {
75 | EditorPrefs.SetBool("notifyNewLocks", false);
76 | }
77 |
78 | if (!EditorPrefs.HasKey("numOfMyLocksDisplayed"))
79 | {
80 | EditorPrefs.SetInt("numOfMyLocksDisplayed", 5);
81 | }
82 |
83 | if (!EditorPrefs.HasKey("gitLocksColorblindMode"))
84 | {
85 | EditorPrefs.SetBool("gitLocksColorblindMode", false);
86 | }
87 |
88 | if (!EditorPrefs.HasKey("gitLocksDebugMode"))
89 | {
90 | EditorPrefs.SetBool("gitLocksDebugMode", false);
91 | }
92 |
93 | if (!EditorPrefs.HasKey("gitLocksShowForceButtons"))
94 | {
95 | EditorPrefs.SetBool("gitLocksShowForceButtons", false);
96 | }
97 |
98 | conflictWarningIgnoreList = new List();
99 |
100 | GetGitVersion();
101 | }
102 |
103 | // Properties
104 | public static List LockedObjectsCache => lockedObjectsCache;
105 |
106 | public static DateTime LastRefresh => lastRefresh;
107 |
108 | public static bool CurrentlyRefreshing => currentlyRefreshing;
109 |
110 | // Methods
111 | public static void CheckLocksRefresh()
112 | {
113 | if (EditorPrefs.GetBool("gitLocksAutoRefreshLocks", true) && DateTime.Now > lastRefresh.AddMinutes(EditorPrefs.GetInt("gitLocksRefreshLocksInterval", 5)) && !EditorApplication.isCompiling && !EditorApplication.isUpdating)
114 | {
115 | RefreshLocks();
116 | }
117 | }
118 |
119 | public static void RefreshLocks()
120 | {
121 | lastRefresh = DateTime.Now;
122 |
123 | // Get the locks asynchronously
124 | currentlyRefreshing = true;
125 | ExecuteNonBlockingProcessTerminal("git", "lfs locks --json");
126 | }
127 |
128 | public static void RefreshCallback(string result)
129 | {
130 | // If empty result, start a simple git lfs locks (no json) to catch potential errors
131 | if (result == "[]")
132 | {
133 | ExecuteNonBlockingProcessTerminal("git", "lfs locks");
134 | }
135 |
136 | // Check that we're receiving what seems to be a JSON result
137 | if (result[0] == '[')
138 | {
139 | // Save old locks
140 | List oldLocks;
141 | if (lockedObjectsCache != null)
142 | {
143 | oldLocks = new List(lockedObjectsCache);
144 | }
145 | else
146 | {
147 | oldLocks = new List();
148 | }
149 |
150 | // Parse the string into a nice list of objects
151 | lockedObjectsCache = ParseJsonIntoLockedObjects(result);
152 |
153 | // Check if we should warn the user about conflicts between uncommited and locked files
154 | BuildUncommitedCache();
155 | if (GetDisplayLocksConflictWarning())
156 | {
157 | string conflictMessage = "The following files are currently locked and you have uncommited changes on them that you'll probably not be able to push:";
158 | bool conflictFound = false;
159 | foreach (GitLocksObject lo in lockedObjectsCache)
160 | {
161 | if (IsLockedObjectConflictingWithUncommitedFile(lo) && !IsFileInConflictIgnoreList(lo.path))
162 | {
163 | conflictFound = true;
164 | conflictMessage += "\n" + lo.path;
165 | AddFileToConflictWarningIgnoreList(lo.path);
166 | }
167 | }
168 | if (conflictFound)
169 | {
170 | EditorUtility.DisplayDialog("Warning", conflictMessage, "OK");
171 | }
172 | }
173 |
174 | // Remove files from ignored warnings list if they're not locked anymore
175 | for (int i = conflictWarningIgnoreList.Count - 1; i >= 0; i--)
176 | {
177 | GitLocksObject lo = GetObjectInLockedCache(conflictWarningIgnoreList[i]);
178 | if (lo == null || lo.IsMine())
179 | {
180 | conflictWarningIgnoreList.RemoveAt(i);
181 | }
182 | }
183 |
184 | // Check if we should warn the user that there are new locks
185 | if (EditorPrefs.GetBool("notifyNewLocks", false))
186 | {
187 | string newLocksString = string.Empty;
188 | foreach (GitLocksObject potentialNewLock in lockedObjectsCache)
189 | {
190 | // Was the lock already there ?
191 | bool lockAlreadyThere = false;
192 | foreach (GitLocksObject lo in oldLocks)
193 | {
194 | if (lo.path == potentialNewLock.path)
195 | {
196 | lockAlreadyThere = true;
197 | break;
198 | }
199 | }
200 |
201 | if (!lockAlreadyThere && potentialNewLock.owner.name != EditorPrefs.GetString("gitLocksHostUsername"))
202 | {
203 | if (newLocksString != string.Empty)
204 | {
205 | newLocksString += "\n";
206 | }
207 |
208 | newLocksString += "[" + potentialNewLock.owner.name + "] " + potentialNewLock.path;
209 | }
210 | }
211 |
212 | if (newLocksString != string.Empty)
213 | {
214 | EditorUtility.DisplayDialog("New locks", newLocksString, "OK");
215 | }
216 | }
217 |
218 | // Sort the locks to show mine first
219 | if (lockedObjectsCache.Count > 0)
220 | {
221 | lockedObjectsCache.Sort(delegate (GitLocksObject a, GitLocksObject b)
222 | {
223 | // ^ is exclusive OR, compare if only one is equal to the git username
224 | if (a.IsMine() ^ b.IsMine())
225 | {
226 | return a.IsMine() ? -1 : 1; // Put my locks first
227 | }
228 | else
229 | {
230 | return a.path.CompareTo(b.path); // When it's the same owner, sort by path
231 | }
232 | });
233 | }
234 | }
235 |
236 | GitLocksDisplay.RepaintAll();
237 | }
238 |
239 | public static string ExecuteProcessTerminal(string processName, string processArguments, out string errorString, bool openTerminal = false)
240 | {
241 | DebugLog("ExecuteProcessTerminal: " + processName + " with the following parameters:\n" + processArguments);
242 |
243 | if (openTerminal)
244 | {
245 | Process p = new Process();
246 | ProcessStartInfo psi = new ProcessStartInfo();
247 | psi.FileName = "cmd.exe";
248 | psi.Arguments = "/k " + processName + " " + processArguments;
249 | p.StartInfo = psi;
250 | p.Start();
251 |
252 | errorString = String.Empty;
253 | return String.Empty;
254 | }
255 | else
256 | {
257 | try
258 | {
259 | using (System.Diagnostics.Process p = new System.Diagnostics.Process())
260 | {
261 | // Redirect the output stream of the child process.
262 | p.StartInfo.CreateNoWindow = !openTerminal;
263 | p.StartInfo.UseShellExecute = openTerminal;
264 | p.StartInfo.RedirectStandardOutput = !openTerminal;
265 | p.StartInfo.RedirectStandardError = !openTerminal;
266 | p.StartInfo.FileName = processName;
267 | p.StartInfo.Arguments = processArguments;
268 |
269 | System.Text.StringBuilder output = new System.Text.StringBuilder();
270 | System.Text.StringBuilder error = new System.Text.StringBuilder();
271 |
272 | using (System.Threading.AutoResetEvent outputWaitHandle = new System.Threading.AutoResetEvent(false))
273 | using (System.Threading.AutoResetEvent errorWaitHandle = new System.Threading.AutoResetEvent(false))
274 | {
275 | p.OutputDataReceived += (sender, e) =>
276 | {
277 | if (e.Data == null)
278 | {
279 | outputWaitHandle.Set();
280 | }
281 | else
282 | {
283 | output.AppendLine(e.Data);
284 | }
285 | };
286 | p.ErrorDataReceived += (sender, e) =>
287 | {
288 | if (e.Data == null)
289 | {
290 | errorWaitHandle.Set();
291 | }
292 | else
293 | {
294 | error.AppendLine(e.Data);
295 | }
296 | };
297 |
298 | p.Start();
299 |
300 | if (!openTerminal)
301 | {
302 | p.BeginOutputReadLine();
303 | p.BeginErrorReadLine();
304 | }
305 |
306 | int timeout = (int)(requestTimeout * 1000);
307 | if (p.WaitForExit(timeout) &&
308 | outputWaitHandle.WaitOne(timeout) &&
309 | errorWaitHandle.WaitOne(timeout))
310 | {
311 | errorString = error.ToString();
312 | return output.ToString();
313 | }
314 | else
315 | {
316 | string err = "Error: Process timed out (" + processName + " " + processArguments + ")";
317 | errorString = err;
318 | UnityEngine.Debug.LogError(err);
319 | return err;
320 | }
321 | }
322 | }
323 | }
324 | catch (Exception e)
325 | {
326 | UnityEngine.Debug.Log(e);
327 | errorString = "e";
328 | return null;
329 | }
330 | }
331 | }
332 |
333 | public static string ExecuteProcessTerminal(string processName, string processArguments, bool openTerminal = false)
334 | {
335 | string errorStr;
336 | string output = ExecuteProcessTerminal(processName, processArguments, out errorStr, openTerminal);
337 | return output;
338 | }
339 |
340 | public static string ExecuteProcessTerminalWithConsole(string processName, string processArguments)
341 | {
342 | string errorStr;
343 | string output = ExecuteProcessTerminal(processName, processArguments, out errorStr);
344 | if (errorStr.Length > 0)
345 | {
346 | EditorUtility.DisplayDialog("Git Lfs Locks Error", errorStr, "OK");
347 | UnityEngine.Debug.LogError(errorStr);
348 | }
349 |
350 | return output;
351 | }
352 |
353 | public static void ExecuteNonBlockingProcessTerminal(string processName, string processArguments)
354 | {
355 | DebugLog("ExecuteNonBlockingProcessTerminal: " + processName + " with the following parameters:\n" + processArguments);
356 |
357 | try
358 | {
359 | // Start the child process.
360 | System.Diagnostics.Process p = new System.Diagnostics.Process();
361 |
362 | // Redirect the output stream of the child process.
363 | p.StartInfo.CreateNoWindow = true;
364 | p.StartInfo.UseShellExecute = false;
365 | p.StartInfo.RedirectStandardOutput = true;
366 | p.StartInfo.RedirectStandardError = true;
367 | p.StartInfo.FileName = processName;
368 | p.StartInfo.Arguments = processArguments;
369 | p.EnableRaisingEvents = true;
370 | p.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler((sender, e) =>
371 | {
372 | // Store the results to be treated on the main thread
373 | if (e.Data != null)
374 | {
375 | refreshCallbackResult = e.Data;
376 | }
377 | });
378 | p.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler((sender, e) =>
379 | {
380 | // Store the errors to be treated on the main thread
381 | if (e.Data != null)
382 | {
383 | refreshCallbackError = e.Data;
384 | }
385 | });
386 | p.Start();
387 | p.BeginOutputReadLine();
388 | p.BeginErrorReadLine();
389 | }
390 | catch (Exception e)
391 | {
392 | UnityEngine.Debug.Log("Error :" + e);
393 | }
394 | }
395 |
396 | public static List ParseJsonIntoLockedObjects(string jsonString)
397 | {
398 | JArray o = JArray.Parse(jsonString);
399 | List llo = Newtonsoft.Json.JsonConvert.DeserializeObject>(jsonString);
400 | return llo;
401 | }
402 |
403 | public static bool IsObjectAvailableToUnlock(UnityEngine.Object obj)
404 | {
405 | return IsObjectAvailableToUnlock(AssetDatabase.GetAssetPath(obj.GetInstanceID()));
406 | }
407 |
408 | public static bool IsObjectAvailableToUnlock(string path)
409 | {
410 | if (lockedObjectsCache == null)
411 | {
412 | return false;
413 | }
414 |
415 | if (ignoredExtensions.Any(s => path.Contains(s)))
416 | {
417 | return false;
418 | }
419 |
420 | foreach (GitLocksObject lo in lockedObjectsCache)
421 | {
422 | if (path.Replace("\\", "/") == lo.path.Replace("\\", "/"))
423 | {
424 | return lo.IsMine();
425 | }
426 | }
427 |
428 | return false;
429 | }
430 |
431 | public static bool IsObjectAvailableToUnlock(GitLocksObject lo)
432 | {
433 | if (lockedObjectsCache == null)
434 | {
435 | return false;
436 | }
437 |
438 | return lo.IsMine();
439 | }
440 |
441 | public static bool IsObjectAvailableToLock(UnityEngine.Object obj)
442 | {
443 | return IsObjectAvailableToLock(AssetDatabase.GetAssetPath(obj.GetInstanceID()));
444 | }
445 |
446 | public static bool IsObjectAvailableToLock(string path)
447 | {
448 | if (lockedObjectsCache == null)
449 | {
450 | return false;
451 | }
452 |
453 | if (ignoredExtensions.Any(s => path.Contains(s)))
454 | {
455 | return false;
456 | }
457 |
458 | foreach (GitLocksObject lo in lockedObjectsCache)
459 | {
460 | if (path.Replace("\\","/") == lo.path.Replace("\\", "/"))
461 | {
462 | return false;
463 | }
464 | }
465 |
466 | return true;
467 | }
468 |
469 | public static GitLocksObject GetObjectInLockedCache(UnityEngine.Object obj)
470 | {
471 | if (lockedObjectsCache == null)
472 | {
473 | return null;
474 | }
475 |
476 | foreach (GitLocksObject lo in lockedObjectsCache)
477 | {
478 | if (obj == lo.GetObjectReference())
479 | {
480 | return lo;
481 | }
482 | }
483 |
484 | return null;
485 | }
486 |
487 | public static GitLocksObject GetObjectInLockedCache(string path)
488 | {
489 | if (lockedObjectsCache == null)
490 | {
491 | return null;
492 | }
493 |
494 | foreach (GitLocksObject lo in lockedObjectsCache)
495 | {
496 | if (path == lo.path)
497 | {
498 | return lo;
499 | }
500 | }
501 |
502 | return null;
503 | }
504 |
505 | public static void LockFile(string path)
506 | {
507 | List list = new List();
508 | list.Add(path);
509 | LockFiles(list);
510 | }
511 |
512 | public static void LockFiles(List paths)
513 | {
514 | // Logs
515 | if (paths.Count > 1)
516 | {
517 | DebugLog("Trying to lock " + paths.Count + " files");
518 | }
519 | else
520 | {
521 | DebugLog("Trying to lock " + paths[0]);
522 | }
523 |
524 | // Split into multiple requests if there are too many files (prevents triggering timeout)
525 | int numOfRequests = (int)Math.Ceiling((float)paths.Count / (float)EditorPrefs.GetInt("gitLocksMaxFilesNumPerRequest", 15));
526 | List pathsStrings = new List(new string[numOfRequests]);
527 | for (int i = 0; i < paths.Count; i++)
528 | {
529 | string p = paths[i];
530 | // Optionally, check if the file we want to lock has been modified on the server
531 | if (EditorPrefs.GetBool("warnIfFileHasBeenModifiedOnServer") && HasFileBeenModifiedOnServer(p))
532 | {
533 | if (EditorUtility.DisplayDialog("File modified on the server", "Warning! The file you want to lock has been modified on the server already, you REALLY should pull before locking or you'll almost certainly get merge conflicts.", "OK, don't lock yet", "I know what I'm doing, lock anyway"))
534 | {
535 | return;
536 | }
537 | }
538 |
539 | int stringIndex = (int)Math.Floor((float)i / (float)EditorPrefs.GetInt("gitLocksMaxFilesNumPerRequest", 15) + Mathf.Epsilon);
540 | pathsStrings[stringIndex] += "\"" + p + "\" ";
541 | }
542 |
543 | // Send each request
544 | foreach (string pathsString in pathsStrings)
545 | {
546 | ExecuteProcessTerminalWithConsole("git", "lfs lock " + pathsString);
547 | }
548 | }
549 |
550 | public static void UnlockFile(string path, bool force = false)
551 | {
552 | List list = new List();
553 | list.Add(path);
554 | UnlockFiles(list, force);
555 | }
556 |
557 | public static void UnlockFiles(List paths, bool force = false)
558 | {
559 | if (paths.Count > 1)
560 | {
561 | DebugLog("Trying to unlock " + paths.Count + " files");
562 | }
563 | else
564 | {
565 | DebugLog("Trying to unlock " + paths[0]);
566 | }
567 |
568 | // Split into multiple requests if there are too many files (prevents triggering timeout)
569 | int numOfRequests = (int)Math.Ceiling((float)paths.Count / (float)EditorPrefs.GetInt("gitLocksMaxFilesNumPerRequest", 15));
570 | List pathsStrings = new List(new string[numOfRequests]);
571 | for (int i = 0; i < paths.Count; i++)
572 | {
573 | string p = paths[i];
574 |
575 | int stringIndex = (int)Math.Floor((float)i / (float)EditorPrefs.GetInt("gitLocksMaxFilesNumPerRequest", 15) + Mathf.Epsilon);
576 | pathsStrings[stringIndex] += "\"" + p + "\" ";
577 | }
578 |
579 | // Send each request
580 | foreach (string pathsString in pathsStrings)
581 | {
582 | ExecuteProcessTerminalWithConsole("git", "lfs unlock " + pathsString + (force ? "--force" : string.Empty));
583 | }
584 | }
585 |
586 | public static void UnlockAllMyLocks()
587 | {
588 | List paths = new List();
589 | foreach (GitLocksObject lo in lockedObjectsCache)
590 | {
591 | if (lo.IsMine())
592 | {
593 | paths.Add(lo.path);
594 | }
595 | }
596 |
597 | UnlockFiles(paths);
598 | RefreshLocks();
599 | }
600 |
601 | public static void UnlockMultipleLocks(List toUnlock)
602 | {
603 | List paths = new List();
604 | foreach (GitLocksObject lo in toUnlock)
605 | {
606 | // Sanity check
607 | if (lo.IsMine())
608 | {
609 | paths.Add(lo.path);
610 | }
611 | }
612 |
613 | UnlockFiles(paths);
614 | RefreshLocks();
615 | }
616 |
617 | public static List GetMyLocks()
618 | {
619 | if (lockedObjectsCache == null || lockedObjectsCache.Count == 0)
620 | {
621 | return null;
622 | }
623 |
624 | List myLocks = new List();
625 | foreach (GitLocksObject lo in lockedObjectsCache)
626 | {
627 | if (lo.IsMine())
628 | {
629 | myLocks.Add(lo);
630 | }
631 | }
632 |
633 | return myLocks;
634 | }
635 |
636 | public static List GetOtherLocks()
637 | {
638 | if (lockedObjectsCache == null || lockedObjectsCache.Count == 0)
639 | {
640 | return null;
641 | }
642 |
643 | List myLocks = new List();
644 | foreach (GitLocksObject lo in lockedObjectsCache)
645 | {
646 | if (!lo.IsMine())
647 | {
648 | myLocks.Add(lo);
649 | }
650 | }
651 |
652 | return myLocks;
653 | }
654 |
655 | public static string GetGitUsername()
656 | {
657 | return EditorPrefs.GetString("gitLocksHostUsername", string.Empty);
658 | }
659 |
660 | public static string GetGitVersion()
661 | {
662 | if (gitVersion == null || gitVersion == string.Empty)
663 | {
664 | gitVersion = ExecuteProcessTerminalWithConsole("git", "--version");
665 | }
666 |
667 | return gitVersion;
668 | }
669 |
670 | public static bool IsGitOutdated()
671 | {
672 | string[] split = GetGitVersion().Split('.');
673 |
674 | int verNum1, verNum2;
675 | bool parse1 = int.TryParse(split[0].Substring(split[0].Length - 1), out verNum1);
676 | bool parse2 = int.TryParse(split[1], out verNum2);
677 | if (parse1 && parse2)
678 | {
679 | // Git for windows version should be greater than 2.30.0 to have the latest authentication manager
680 | return verNum1 < 2 || verNum2 < 30;
681 | }
682 | else
683 | {
684 | UnityEngine.Debug.LogWarning("GitLocks couldn't parse the Git for Windows version properly");
685 | return true;
686 | }
687 | }
688 |
689 | public static bool GetDisplayLocksConflictWarning()
690 | {
691 | return EditorPrefs.GetBool("displayLocksConflictWarning");
692 | }
693 |
694 | public static string GetAssetPathFromPrefabGameObject(int instanceID)
695 | {
696 | var gameObject = EditorUtility.InstanceIDToObject(instanceID) as GameObject;
697 | return GetAssetPathFromPrefabGameObject(gameObject);
698 | }
699 |
700 | public static string GetAssetPathFromPrefabGameObject(GameObject gameObject)
701 | {
702 | if (gameObject != null)
703 | {
704 | return PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(gameObject);
705 | }
706 |
707 | return string.Empty;
708 | }
709 |
710 | public static bool IsObjectPrefabRoot(int instanceID)
711 | {
712 | var gameObject = EditorUtility.InstanceIDToObject(instanceID) as GameObject;
713 | return IsObjectPrefabRoot(gameObject);
714 | }
715 |
716 | public static bool IsObjectPrefabRoot(GameObject gameObject)
717 | {
718 | if (gameObject != null)
719 | {
720 | return PrefabUtility.GetNearestPrefabInstanceRoot(gameObject) == gameObject;
721 | }
722 |
723 | return false;
724 | }
725 |
726 | public static Scene GetSceneFromInstanceID(int instanceID)
727 | {
728 | for (int index = 0; index < SceneManager.sceneCount; index++)
729 | {
730 | var scene = SceneManager.GetSceneAt(index);
731 | if (scene.handle == instanceID)
732 | {
733 | return scene;
734 | }
735 | }
736 |
737 | return default;
738 | }
739 |
740 | public static bool IsLockedObjectConflictingWithUncommitedFile(GitLocksObject lo)
741 | {
742 | if (uncommitedFilesCache == null || uncommitedFilesCache.Count == 0)
743 | {
744 | return false;
745 | }
746 |
747 | if (GetGitUsername() == null || GetGitUsername() == string.Empty)
748 | {
749 | return false;
750 | }
751 |
752 | foreach (string uncommitedPath in uncommitedFilesCache)
753 | {
754 | if (lo.path == uncommitedPath && !lo.IsMine())
755 | {
756 | return true;
757 | }
758 | }
759 |
760 | return false;
761 | }
762 |
763 | public static void SetUncommitedCacheDirty()
764 | {
765 | uncommitedFilesCacheDirty = true;
766 | }
767 |
768 | public static void BuildUncommitedCache()
769 | {
770 | if (uncommitedFilesCache != null)
771 | {
772 | uncommitedFilesCache.Clear();
773 | }
774 | else
775 | {
776 | uncommitedFilesCache = new List();
777 | }
778 |
779 | char[] splitter = { '\n' };
780 |
781 | // Staged
782 | string output = ExecuteProcessTerminal("git", "diff --name-only --staged");
783 | string[] lines = output.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
784 | List filesCandidates = new List(lines);
785 |
786 | // Not staged
787 | output = ExecuteProcessTerminal("git", "diff --name-only");
788 | lines = output.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
789 | filesCandidates.AddRange(lines);
790 |
791 | // Check all lines to see if they correspond to files and add them to the cache if so
792 | foreach (string file in filesCandidates)
793 | {
794 | // Clean and format strings
795 | string tmpFile = file;
796 | tmpFile = tmpFile.Trim();
797 | tmpFile = tmpFile.Replace("\\r", string.Empty);
798 | tmpFile = tmpFile.Replace("\\n", string.Empty);
799 | string fullPath = Application.dataPath.Replace("Assets", string.Empty) + tmpFile;
800 | fullPath = fullPath.Replace("/", "\\");
801 |
802 | if (System.IO.File.Exists(fullPath))
803 | {
804 | uncommitedFilesCache.Add(tmpFile);
805 | }
806 | }
807 |
808 | uncommitedFilesCacheDirty = false;
809 |
810 | GitLocksDisplay.RepaintAll();
811 | }
812 |
813 | public static void BuildModifiedOnServerCache()
814 | {
815 | if (modifiedOnServerFilesCache != null)
816 | {
817 | modifiedOnServerFilesCache.Clear();
818 | }
819 | else
820 | {
821 | modifiedOnServerFilesCache = new List();
822 | }
823 |
824 | char[] splitter = { '\n' };
825 |
826 | // Construct list of branches to check
827 | HashSet branchesToCheck = new HashSet();
828 |
829 | // Add optional branches to check set in preferences
830 | if (EditorPrefs.HasKey("gitLocksBranchesToCheck"))
831 | {
832 | string fullString = EditorPrefs.GetString("gitLocksBranchesToCheck");
833 | string[] array = fullString.Split(',');
834 | foreach (string branch in array)
835 | {
836 | if (branch != string.Empty)
837 | {
838 | branchesToCheck.Add(branch);
839 | }
840 | }
841 | }
842 |
843 | // Add current branch name
844 | string currentBranch = GetCurrentBranch();
845 | branchesToCheck.Add(currentBranch);
846 |
847 | foreach (string branch in branchesToCheck)
848 | {
849 | // Fetch
850 | ExecuteProcessTerminal("git", "git fetch origin " + branch);
851 |
852 | // List all distant commits
853 | string output = ExecuteProcessTerminal("git", "rev-list " + currentBranch + "..origin/" + branch);
854 | string[] lines = output.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
855 | List commits = new List(lines);
856 |
857 | // Check all commits
858 | foreach (string commit in commits)
859 | {
860 | // Add all files in commit to the list
861 | string filesOutput = ExecuteProcessTerminal("git", "diff-tree --no-commit-id --name-only -r " + commit.Replace("\r", ""));
862 | string[] files = filesOutput.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
863 |
864 | foreach (string file in files)
865 | {
866 | // Check that every file exists (sanity)
867 | if (System.IO.File.Exists(file.Replace("\r", "")))
868 | {
869 | modifiedOnServerFilesCache.Add(file.Replace("\r", ""));
870 | }
871 | }
872 | }
873 | }
874 | }
875 |
876 | public static bool HasFileBeenModifiedOnServer(string filePath)
877 | {
878 | BuildModifiedOnServerCache();
879 | return modifiedOnServerFilesCache.Contains(filePath);
880 | }
881 |
882 | public static void AddFileToConflictWarningIgnoreList(string path)
883 | {
884 | conflictWarningIgnoreList.Add(path);
885 | }
886 |
887 | public static bool IsFileInConflictIgnoreList(string path)
888 | {
889 | return conflictWarningIgnoreList.Contains(path);
890 | }
891 |
892 | public static bool IsEnabled()
893 | {
894 | return EditorPrefs.GetBool("gitLocksEnabled", false);
895 | }
896 |
897 | public static string GetCurrentBranch()
898 | {
899 | char[] splitter = { '\n' };
900 | string currentBranch = ExecuteProcessTerminal("git", "rev-parse --abbrev-ref HEAD");
901 | currentBranch = currentBranch.Split(splitter)[0].Replace("\r", "");
902 | return currentBranch;
903 | }
904 |
905 | private static void Update()
906 | {
907 | if (!IsEnabled() || EditorApplication.isUpdating || EditorApplication.isCompiling)
908 | {
909 | return; // Early return if the whole tool is disabled of if the Editor is not available
910 | }
911 |
912 | if (uncommitedFilesCacheDirty)
913 | {
914 | BuildUncommitedCache();
915 | }
916 |
917 | if (refreshCallbackResult != null)
918 | {
919 | if (refreshCallbackResult != string.Empty)
920 | {
921 | RefreshCallback(refreshCallbackResult);
922 | }
923 |
924 | currentlyRefreshing = false;
925 | refreshCallbackResult = null;
926 | }
927 |
928 | if (refreshCallbackError != null && refreshCallbackError != string.Empty)
929 | {
930 | if (!EditorUtility.DisplayDialog("Git Lfs Locks Error", "Git lfs locks error :\n\n" + refreshCallbackError + "\n\nIf it's your first time using the tool, you should probably setup the credentials manager", "OK", "Setup credentials"))
931 | {
932 | DebugLog("Setup credentials manager");
933 | ExecuteProcessTerminalWithConsole("git", "config --global credential.helper manager");
934 | }
935 |
936 | refreshCallbackError = null;
937 | }
938 | }
939 |
940 | private static bool WantsToQuit()
941 | {
942 | if (!IsEnabled())
943 | {
944 | return true; // Early return if the whole tool is disabled
945 | }
946 |
947 | if (EditorPrefs.GetBool("warnIfIStillOwnLocksOnQuit") && GetMyLocks() != null && GetMyLocks().Count > 0)
948 | {
949 | if (EditorUtility.DisplayDialog("Remaining locks", "You still own locks on some files, do you want to quit anyway ?", "Yes", "No, take me back"))
950 | {
951 | return true;
952 | }
953 | else
954 | {
955 | GitLocksDisplay locksWindow = (GitLocksDisplay)EditorWindow.GetWindow(typeof(GitLocksDisplay), false, "Git Locks");
956 | return false;
957 | }
958 | }
959 | else
960 | {
961 | return true;
962 | }
963 | }
964 |
965 | private static void DebugLog(string s)
966 | {
967 | if (EditorPrefs.GetBool("gitLocksDebugMode", false))
968 | {
969 | UnityEngine.Debug.Log(s);
970 | }
971 | }
972 | }
--------------------------------------------------------------------------------
/Editor/Scripts/GitLocks.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d4155b722e550bc4792e1e147bea5ae0
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/Scripts/GitLocksAssetPostprocessor.cs:
--------------------------------------------------------------------------------
1 | // All rights reserved.
2 |
3 | using System.Collections;
4 | using System.Collections.Generic;
5 | using UnityEngine;
6 |
7 | public class GitLocksAssetPostprocessor : UnityEditor.AssetPostprocessor
8 | {
9 | private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
10 | {
11 | foreach (string str in importedAssets)
12 | {
13 | if (GitLocks.GetObjectInLockedCache(str) != null)
14 | {
15 | GitLocks.SetUncommitedCacheDirty();
16 | }
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Editor/Scripts/GitLocksAssetPostprocessor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 2eca742852b5d7945a46fd16eb986a5a
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/Scripts/GitLocksDisplay.cs:
--------------------------------------------------------------------------------
1 | // All rights reserved.
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using UnityEditor;
7 | using UnityEngine;
8 |
9 | [InitializeOnLoad]
10 | public class GitLocksDisplay : EditorWindow
11 | {
12 | private static Texture greenLockIcon;
13 | private static Texture orangeLockIcon;
14 | private static Texture redLockIcon;
15 | private static Texture mixedLockIcon;
16 |
17 | // Interface sizes
18 | private static float unlockButtonWidth = 65;
19 | private static float forceUnlockButtonWidth = 95;
20 | private static float lockIconWidth = 18;
21 | private static float scrollbarWidth = 20; // In case the scoll view triggers
22 | private static float checkboxWidth = 30;
23 |
24 | private UnityEngine.Object objectToLock;
25 | private Vector2 scrollPosMine = Vector2.zero;
26 | private Vector2 scrollPosOthers = Vector2.zero;
27 |
28 | private static List selectedLocks;
29 |
30 | // Show git history
31 | private static int showHistoryMaxNumOfFilesBeforeWarning = 5;
32 |
33 | static GitLocksDisplay()
34 | {
35 | // Add our own GUI to the project and hierarchy windows
36 | EditorApplication.projectWindowItemOnGUI += DrawProjectLocks;
37 | EditorApplication.hierarchyWindowItemOnGUI += DrawHierarchyLocks;
38 | }
39 |
40 | [MenuItem("Window/Git Locks")]
41 | public static void ShowWindow()
42 | {
43 | // Show existing window instance. If one doesn't exist, make one.
44 | EditorWindow.GetWindow(typeof(GitLocksDisplay), false, "Git Locks");
45 | }
46 |
47 | public static void RepaintAll()
48 | {
49 | EditorApplication.RepaintHierarchyWindow();
50 | EditorApplication.RepaintProjectWindow();
51 | if (EditorWindow.HasOpenInstances())
52 | {
53 | EditorWindow locksWindow = GetWindow(typeof(GitLocksDisplay), false, "Git Locks", false);
54 | locksWindow.Repaint();
55 | }
56 | }
57 |
58 | public static void DrawUILine(Color color, int thickness = 2, int padding = 10)
59 | {
60 | Rect r = EditorGUILayout.GetControlRect(GUILayout.Height(padding + thickness));
61 | r.height = thickness;
62 | r.y += padding / 2;
63 | r.x -= 2;
64 | r.width += 6;
65 | EditorGUI.DrawRect(r, color);
66 | }
67 |
68 | public static Texture GetIconForLockedObject(GitLocksObject lo)
69 | {
70 | if (lo == null)
71 | {
72 | return null;
73 | }
74 |
75 | bool isLockConflictingWithUncommitedFile = GitLocks.IsLockedObjectConflictingWithUncommitedFile(lo);
76 |
77 | if (lo.IsMine())
78 | {
79 | return GetGreenLockIcon();
80 | }
81 | else if (isLockConflictingWithUncommitedFile)
82 | {
83 | return GetRedLockIcon();
84 | }
85 | else
86 | {
87 | return GetOrangeLockIcon();
88 | }
89 | }
90 |
91 | public static void DisplayLockIcon(string path, Rect selectionRect, float offset, bool small = false)
92 | {
93 | var frame = new Rect(selectionRect);
94 |
95 | // Handle files
96 | GitLocksObject lo = GitLocks.GetObjectInLockedCache(path);
97 | if (lo != null)
98 | {
99 | frame.x += offset + (small ? 3f : 0f);
100 | frame.width = small ? 12f : 18f;
101 |
102 | Texture lockTexture = GetIconForLockedObject(lo);
103 | string tooltip;
104 |
105 | // Fill tooltip
106 | if (lo.IsMine())
107 | {
108 | tooltip = "Locked by me";
109 | }
110 | else if (GitLocks.IsLockedObjectConflictingWithUncommitedFile(lo))
111 | {
112 | tooltip = "Conflicting with lock by " + lo.owner.name;
113 | }
114 | else
115 | {
116 | tooltip = "Locked by " + lo.owner.name;
117 | }
118 |
119 | if (GUI.Button(frame, new GUIContent(lockTexture, tooltip), GUI.skin.label))
120 | {
121 | if (lo.IsMine())
122 | {
123 | if (!EditorUtility.DisplayDialog("Asset locked by you", "You have locked this asset, you're safe working on it.", "OK", "Unlock"))
124 | {
125 | GitLocks.UnlockFile(lo.path);
126 | GitLocks.RefreshLocks();
127 | }
128 | }
129 | else if (GitLocks.IsLockedObjectConflictingWithUncommitedFile(lo))
130 | {
131 | EditorUtility.DisplayDialog("Asset locked by someone else and conflicting", "User " + lo.owner.name + " has locked this asset (" + lo.GetLockDateTimeString() + ") and you have uncommited modifications: you should probably discard them as you won't be able to push them.", "OK");
132 | }
133 | else
134 | {
135 | EditorUtility.DisplayDialog("Asset locked by someone else", "User " + lo.owner.name + " has locked this asset (" + lo.GetLockDateTimeString() + "), you cannot work on it.", "OK");
136 | }
137 | }
138 | }
139 |
140 | // Handle folders
141 | if (Directory.Exists(path) && GitLocks.LockedObjectsCache != null)
142 | {
143 | bool containsOneOfMyLocks = false;
144 | bool containsOneOfOtherLocks = false;
145 | bool containsOneConflictingLock = false;
146 | foreach (GitLocksObject dlo in GitLocks.LockedObjectsCache)
147 | {
148 | string folderPath = path + "/";
149 | if (dlo.path.Contains(folderPath))
150 | {
151 | if (dlo.IsMine())
152 | {
153 | containsOneOfMyLocks = true;
154 | }
155 | else if (GitLocks.IsLockedObjectConflictingWithUncommitedFile(dlo))
156 | {
157 | containsOneConflictingLock = true;
158 | containsOneOfOtherLocks = true;
159 | }
160 | else
161 | {
162 | containsOneOfOtherLocks = true;
163 | }
164 | if (containsOneOfMyLocks && containsOneOfOtherLocks)
165 | {
166 | break;
167 | }
168 | }
169 | }
170 |
171 | if (containsOneOfMyLocks || containsOneOfOtherLocks)
172 | {
173 | frame.x += offset + 15;
174 | frame.width = 15f;
175 | string tooltip;
176 | Texture lockTexture;
177 |
178 | if (containsOneOfMyLocks && containsOneOfOtherLocks)
179 | {
180 | lockTexture = GetMixedLockIcon();
181 | tooltip = "Folder contains files locked by me and others";
182 | }
183 | else if (containsOneOfMyLocks)
184 | {
185 | lockTexture = GetGreenLockIcon();
186 | tooltip = "Folder contains files locked by me";
187 | }
188 | else if (containsOneConflictingLock)
189 | {
190 | lockTexture = GetRedLockIcon();
191 | tooltip = "Folder contains conflicting files";
192 | }
193 | else
194 | {
195 | lockTexture = GetOrangeLockIcon();
196 | tooltip = "Folder contains files locked by others";
197 | }
198 |
199 | GUI.Button(frame, new GUIContent(lockTexture, tooltip), GUI.skin.label);
200 | }
201 | }
202 | }
203 |
204 | // -----------------------
205 | // Project window features
206 | // -----------------------
207 | [MenuItem("Assets/Git LFS Lock %#l", false, 1100)]
208 | private static void ItemMenuLock()
209 | {
210 | UnityEngine.Object[] selected = Selection.GetFiltered(SelectionMode.DeepAssets);
211 | List paths = new List();
212 | foreach (UnityEngine.Object o in selected)
213 | {
214 | string path = AssetDatabase.GetAssetPath(o.GetInstanceID());
215 | if (!string.IsNullOrEmpty(path) && Directory.Exists(path))
216 | {
217 | continue; // Folders are not lockable, skip this asset
218 | }
219 |
220 | paths.Add(path);
221 | }
222 |
223 | GitLocks.LockFiles(paths);
224 | GitLocks.RefreshLocks();
225 | }
226 |
227 | [MenuItem("Assets/Git LFS Lock %#l", true)]
228 | private static bool ValidateItemMenuLock()
229 | {
230 | if (!GitLocks.IsEnabled())
231 | {
232 | return false; // Early return if the whole tool is disabled
233 | }
234 |
235 | // Don't allow if the locked cache hasn't been built
236 | if (GitLocks.LastRefresh <= DateTime.MinValue)
237 | {
238 | return false;
239 | }
240 |
241 | UnityEngine.Object[] selected = Selection.GetFiltered(SelectionMode.Assets);
242 | foreach (UnityEngine.Object o in selected)
243 | {
244 | string path = AssetDatabase.GetAssetPath(o.GetInstanceID());
245 | if (Directory.Exists(path))
246 | {
247 | foreach (GitLocksObject lo in GitLocks.LockedObjectsCache)
248 | {
249 | string folderPath = path + "/";
250 | if (lo.path.Contains(folderPath))
251 | {
252 | return false;
253 | }
254 | }
255 | }
256 | else if (!GitLocks.IsObjectAvailableToLock(path))
257 | {
258 | return false;
259 | }
260 | }
261 |
262 | return true;
263 | }
264 |
265 | [MenuItem("Assets/Git LFS Unlock %#u", false, 1101)]
266 | private static void ItemMenuUnlock()
267 | {
268 | List paths = new List();
269 | UnityEngine.Object[] selected = Selection.GetFiltered(SelectionMode.Assets);
270 | foreach (UnityEngine.Object o in selected)
271 | {
272 | string path = AssetDatabase.GetAssetPath(o.GetInstanceID());
273 | if (Directory.Exists(path))
274 | {
275 | foreach (GitLocksObject lo in GitLocks.LockedObjectsCache)
276 | {
277 | string folderPath = path + "/";
278 | if (lo.path.Contains(folderPath))
279 | {
280 | if (GitLocks.IsObjectAvailableToUnlock(lo))
281 | {
282 | paths.Add(lo.path);
283 | }
284 | }
285 | }
286 | }
287 | else if (GitLocks.IsObjectAvailableToUnlock(path))
288 | {
289 | paths.Add(path);
290 | }
291 | }
292 |
293 | GitLocks.UnlockFiles(paths);
294 | GitLocks.RefreshLocks();
295 | }
296 |
297 | [MenuItem("Assets/Git LFS Unlock %#u", true)]
298 | private static bool ValidateItemMenuUnlock()
299 | {
300 | if (!GitLocks.IsEnabled())
301 | {
302 | return false; // Early return if the whole tool is disabled
303 | }
304 |
305 | // Don't allow if the locked cache hasn't been built
306 | if (GitLocks.LastRefresh <= DateTime.MinValue)
307 | {
308 | return false;
309 | }
310 |
311 | UnityEngine.Object[] selected = Selection.GetFiltered(SelectionMode.Assets);
312 | bool foundObjectToUnlock = false;
313 | foreach (UnityEngine.Object o in selected)
314 | {
315 | string path = AssetDatabase.GetAssetPath(o.GetInstanceID());
316 | if (Directory.Exists(path))
317 | {
318 | foreach (GitLocksObject lo in GitLocks.LockedObjectsCache)
319 | {
320 | string folderPath = path + "/";
321 | if (lo.path.Contains(folderPath))
322 | {
323 | if (lo.IsMine())
324 | {
325 | foundObjectToUnlock = true;
326 | }
327 | else
328 | {
329 | return false;
330 | }
331 | }
332 | }
333 | }
334 | else if (GitLocks.IsObjectAvailableToUnlock(path))
335 | {
336 | foundObjectToUnlock = true;
337 | }
338 | }
339 |
340 | return foundObjectToUnlock;
341 | }
342 |
343 | // -------------------------
344 | // Hierarchy window features
345 | // -------------------------
346 | [MenuItem("GameObject/Git LFS Lock", false, 40)]
347 | private static void ItemMenuLockHierarchy()
348 | {
349 | List paths = new List();
350 | foreach (UnityEngine.Object o in Selection.objects)
351 | {
352 | string path = GitLocks.GetAssetPathFromPrefabGameObject(o.GetInstanceID());
353 | paths.Add(path);
354 | }
355 |
356 | GitLocks.LockFiles(paths);
357 |
358 | // Clear the selection to make sure it's called only once
359 | Selection.objects = null;
360 |
361 | GitLocks.RefreshLocks();
362 | }
363 |
364 | [MenuItem("GameObject/Git LFS Lock", true)]
365 | private static bool ValidateItemMenuLockHierarchy()
366 | {
367 | if (!GitLocks.IsEnabled())
368 | {
369 | return false; // Early return if the whole tool is disabled
370 | }
371 |
372 | // Don't allow if the locked cache hasn't been built
373 | if (GitLocks.LastRefresh <= DateTime.MinValue)
374 | {
375 | return false;
376 | }
377 |
378 | foreach (UnityEngine.Object o in Selection.objects)
379 | {
380 | if (o == null)
381 | {
382 | return false;
383 | }
384 |
385 | string path = GitLocks.GetAssetPathFromPrefabGameObject(o.GetInstanceID());
386 | if (path == null || path == string.Empty || !GitLocks.IsObjectAvailableToLock(path))
387 | {
388 | return false;
389 | }
390 | }
391 |
392 | return true;
393 | }
394 |
395 | [MenuItem("GameObject/Git LFS Unlock", false, 41)]
396 | private static void ItemMenuUnlockHierarchy()
397 | {
398 | List paths = new List();
399 | foreach (UnityEngine.Object o in Selection.objects)
400 | {
401 | string path = GitLocks.GetAssetPathFromPrefabGameObject(o.GetInstanceID());
402 | paths.Add(path);
403 | }
404 |
405 | GitLocks.UnlockFiles(paths);
406 |
407 | // Clear the selection to make sure it's called only once
408 | Selection.objects = null;
409 |
410 | GitLocks.RefreshLocks();
411 | }
412 |
413 | [MenuItem("GameObject/Git LFS Unlock", true)]
414 | private static bool ValidateItemMenuUnlockHierarchy()
415 | {
416 | if (!GitLocks.IsEnabled())
417 | {
418 | return false; // Early return if the whole tool is disabled
419 | }
420 |
421 | // Don't allow if the locked cache hasn't been built
422 | if (GitLocks.LastRefresh <= DateTime.MinValue)
423 | {
424 | return false;
425 | }
426 |
427 | foreach (UnityEngine.Object o in Selection.objects)
428 | {
429 | if (o == null)
430 | {
431 | return false;
432 | }
433 |
434 | string path = GitLocks.GetAssetPathFromPrefabGameObject(o.GetInstanceID());
435 | if (path == null || path == string.Empty || !GitLocks.IsObjectAvailableToUnlock(path))
436 | {
437 | return false;
438 | }
439 | }
440 |
441 | return true;
442 | }
443 |
444 | // -------------------------------------------
445 | // Draw icons in hierarchy and project windows
446 | // -------------------------------------------
447 | private static void DrawProjectLocks(string guid, Rect selectionRect)
448 | {
449 | if (!GitLocks.IsEnabled())
450 | {
451 | return; // Early return if the whole tool is disabled
452 | }
453 |
454 | GitLocks.CheckLocksRefresh();
455 |
456 | string path = AssetDatabase.GUIDToAssetPath(guid);
457 |
458 | DisplayLockIcon(path, selectionRect, -12f);
459 | }
460 |
461 | private static void DrawHierarchyLocks(int instanceID, Rect selectionRect)
462 | {
463 | if (!GitLocks.IsEnabled())
464 | {
465 | return; // Early return if the whole tool is disabled
466 | }
467 |
468 | GitLocks.CheckLocksRefresh();
469 |
470 | string path = string.Empty;
471 | bool small = false;
472 |
473 | // Handle scenes
474 | path = GitLocks.GetSceneFromInstanceID(instanceID).path;
475 |
476 | // Handle prefabs
477 | string tmpPath = GitLocks.GetAssetPathFromPrefabGameObject(instanceID);
478 | if (tmpPath != string.Empty)
479 | {
480 | path = tmpPath;
481 | small = !GitLocks.IsObjectPrefabRoot(instanceID);
482 | }
483 |
484 | // Display
485 | if (path != string.Empty)
486 | {
487 | DisplayLockIcon(path, selectionRect, -30f, small);
488 | }
489 | }
490 |
491 | // ------------
492 | // File history
493 | // ------------
494 | [MenuItem("Assets/Show Git History", false, 1101)]
495 | private static void ItemMenuGitHistory()
496 | {
497 | UnityEngine.Object[] selected = Selection.GetFiltered(SelectionMode.Assets);
498 |
499 | // Display a warning if you're about to open many CLIs or browser tabs to prevent slowing down your computer if you misclick
500 | if (selected.Length <= showHistoryMaxNumOfFilesBeforeWarning || EditorUtility.DisplayDialog("Are you sure?", "More than " + showHistoryMaxNumOfFilesBeforeWarning + " files have been selected, are you sure you want to open the history for all of them?", "Yes", "Cancel"))
501 | {
502 | foreach (UnityEngine.Object o in selected)
503 | {
504 | string path = AssetDatabase.GetAssetPath(o.GetInstanceID());
505 | if (Directory.Exists(path))
506 | {
507 | continue; // Folders are not lockable, skip this asset
508 | }
509 |
510 | if (EditorPrefs.GetBool("gitLocksShowHistoryInBrowser", false))
511 | {
512 | string url = EditorPrefs.GetString("gitLocksShowHistoryInBrowserUrl");
513 | if (url != string.Empty && url.Contains("$branch") && url.Contains("$assetPath"))
514 | {
515 | url = url.Replace("$branch", GitLocks.GetCurrentBranch());
516 | url = url.Replace("$assetPath", path);
517 | UnityEngine.Application.OpenURL(url);
518 | }
519 | else
520 | {
521 | UnityEngine.Debug.LogError("URL was not formatted correctly to show the file's history in your browser: it must be formatted like https://github.com/MyUserName/MyRepo/blob/$branch/$assetPath");
522 | }
523 | }
524 | else
525 | {
526 | GitLocks.ExecuteProcessTerminal("git", "log \"" + path + "\"", true);
527 | }
528 | }
529 | }
530 | }
531 |
532 | [MenuItem("Assets/Show Git History", true)]
533 | private static bool ValidateItemMenuGitHistory()
534 | {
535 | UnityEngine.Object[] selected = Selection.GetFiltered(SelectionMode.Assets);
536 | foreach (UnityEngine.Object o in selected)
537 | {
538 | string path = AssetDatabase.GetAssetPath(o.GetInstanceID());
539 | if (Directory.Exists(path))
540 | {
541 | return false;
542 | }
543 | }
544 |
545 | return true;
546 | }
547 |
548 | // -----------
549 | // Toolbox
550 | // -----------
551 | private static void DrawLockedObjectLine(GitLocksObject lo, bool myLock = false)
552 | {
553 | UnityEngine.Object lockedObj = lo.GetObjectReference();
554 | if (lockedObj != null)
555 | {
556 | float totalOtherWidth = EditorGUIUtility.currentViewWidth - lockIconWidth - scrollbarWidth - checkboxWidth;
557 | totalOtherWidth -= EditorPrefs.GetBool("gitLocksShowForceButtons") ? forceUnlockButtonWidth : 0;
558 | totalOtherWidth -= myLock ? unlockButtonWidth : 0;
559 | float othersWidth = totalOtherWidth / 3;
560 |
561 | GUILayout.BeginHorizontal();
562 |
563 | Texture lockTexture = GetIconForLockedObject(lo);
564 |
565 | // Checkboxes for custom selection
566 | if (myLock)
567 | {
568 | if (selectedLocks == null)
569 | {
570 | selectedLocks = new List();
571 | }
572 | bool checkbox = GUILayout.Toggle(selectedLocks.Contains(lo), "");
573 | if (checkbox && !selectedLocks.Contains(lo))
574 | {
575 | selectedLocks.Add(lo);
576 | }
577 | else if (!checkbox && selectedLocks.Contains(lo))
578 | {
579 | selectedLocks.Remove(lo);
580 | }
581 | }
582 |
583 | GUILayout.Button(lockTexture, GUI.skin.label, GUILayout.Height(18), GUILayout.Width(18));
584 |
585 | EditorGUI.BeginDisabledGroup(true);
586 | EditorGUILayout.ObjectField(lockedObj, lockedObj.GetType(), false, GUILayout.Width(othersWidth));
587 | EditorGUI.EndDisabledGroup();
588 |
589 | EditorGUILayout.LabelField(new GUIContent(lo.path, lo.path), GUILayout.Width(othersWidth));
590 |
591 | GUILayout.Label(new GUIContent(lo.owner.name, lo.GetLockDateTimeString()), GUILayout.Width(othersWidth));
592 |
593 | if (myLock)
594 | {
595 | if (GUILayout.Button("Unlock"))
596 | {
597 | GitLocks.UnlockFile(lo.path);
598 | GitLocks.RefreshLocks();
599 | }
600 | }
601 |
602 | if (EditorPrefs.GetBool("gitLocksShowForceButtons"))
603 | {
604 | if (GUILayout.Button("Force unlock"))
605 | {
606 | if (EditorUtility.DisplayDialog("Force unlock ?", "Are you sure you want to force the unlock ? It may mess with a teammate's work !", "Yes, I know the risks", "Cancel, I'm not sure"))
607 | {
608 | GitLocks.UnlockFile(lo.path, true);
609 | GitLocks.RefreshLocks();
610 | }
611 | }
612 | }
613 |
614 | GUILayout.EndHorizontal();
615 | }
616 | }
617 |
618 | private static Texture GetGreenLockIcon(bool forceReload = false)
619 | {
620 | if (greenLockIcon == null || forceReload)
621 | {
622 | if (EditorPrefs.HasKey("gitLocksColorblindMode") && EditorPrefs.GetBool("gitLocksColorblindMode"))
623 | {
624 | greenLockIcon = (Texture)AssetDatabase.LoadAssetAtPath("Packages/com.tomduchene.unity-git-locks/Editor/Textures/greenLock_cb.png", typeof(Texture));
625 | }
626 | else
627 | {
628 | greenLockIcon = (Texture)AssetDatabase.LoadAssetAtPath("Packages/com.tomduchene.unity-git-locks/Editor/Textures/greenLock.png", typeof(Texture));
629 | }
630 | }
631 |
632 | return greenLockIcon;
633 | }
634 |
635 | private static Texture GetOrangeLockIcon(bool forceReload = false)
636 | {
637 | if (orangeLockIcon == null || forceReload)
638 | {
639 | if (EditorPrefs.HasKey("gitLocksColorblindMode") && EditorPrefs.GetBool("gitLocksColorblindMode"))
640 | {
641 | orangeLockIcon = (Texture)AssetDatabase.LoadAssetAtPath("Packages/com.tomduchene.unity-git-locks/Editor/Textures/orangeLock_cb.png", typeof(Texture));
642 | }
643 | else
644 | {
645 | orangeLockIcon = (Texture)AssetDatabase.LoadAssetAtPath("Packages/com.tomduchene.unity-git-locks/Editor/Textures/orangeLock.png", typeof(Texture));
646 | }
647 | }
648 |
649 | return orangeLockIcon;
650 | }
651 |
652 | private static Texture GetRedLockIcon(bool forceReload = false)
653 | {
654 | if (redLockIcon == null || forceReload)
655 | {
656 | if (EditorPrefs.HasKey("gitLocksColorblindMode") && EditorPrefs.GetBool("gitLocksColorblindMode"))
657 | {
658 | redLockIcon = (Texture)AssetDatabase.LoadAssetAtPath("Packages/com.tomduchene.unity-git-locks/Editor/Textures/redLock_cb.png", typeof(Texture));
659 | }
660 | else
661 | {
662 | redLockIcon = (Texture)AssetDatabase.LoadAssetAtPath("Packages/com.tomduchene.unity-git-locks/Editor/Textures/redLock.png", typeof(Texture));
663 | }
664 | }
665 |
666 | return redLockIcon;
667 | }
668 |
669 | private static Texture GetMixedLockIcon(bool forceReload = false)
670 | {
671 | if (mixedLockIcon == null || forceReload)
672 | {
673 | if (EditorPrefs.HasKey("gitLocksColorblindMode") && EditorPrefs.GetBool("gitLocksColorblindMode"))
674 | {
675 | mixedLockIcon = (Texture)AssetDatabase.LoadAssetAtPath("Packages/com.tomduchene.unity-git-locks/Editor/Textures/mixedLock_cb.png", typeof(Texture));
676 | }
677 | else
678 | {
679 | mixedLockIcon = (Texture)AssetDatabase.LoadAssetAtPath("Packages/com.tomduchene.unity-git-locks/Editor/Textures/mixedLock.png", typeof(Texture));
680 | }
681 | }
682 |
683 | return mixedLockIcon;
684 | }
685 |
686 | public static void RefreshLockIcons()
687 | {
688 | GetGreenLockIcon(true);
689 | GetOrangeLockIcon(true);
690 | GetRedLockIcon(true);
691 | GetMixedLockIcon(true);
692 | }
693 |
694 | // ------------------------
695 | // Git lock window features
696 | // ------------------------
697 | private void OnGUI()
698 | {
699 | if (!GitLocks.IsEnabled())
700 | {
701 | GUILayout.Label("Tool disabled", EditorStyles.wordWrappedLabel);
702 | if (GUILayout.Button("Enable in preferences"))
703 | {
704 | SettingsService.OpenUserPreferences("Preferences/Git Locks");
705 | }
706 | }
707 | else
708 | {
709 | GitLocks.CheckLocksRefresh();
710 |
711 | EditorGUILayout.Space(10);
712 |
713 | if (GitLocks.GetGitUsername() == string.Empty)
714 | {
715 | // If username hasn't been set, show this window instead of the main one to ask the user to input it's username
716 | GUILayout.Label("You need to setup your Git LFS host username for the tool to work properly, most likely your Github username", EditorStyles.wordWrappedLabel);
717 | GUILayout.BeginHorizontal();
718 | if (GUILayout.Button("Go on github"))
719 | {
720 | UnityEngine.Application.OpenURL("https://github.com/");
721 | }
722 |
723 | if (GUILayout.Button("Setup username in preferences"))
724 | {
725 | SettingsService.OpenUserPreferences("Preferences/Git Locks");
726 | }
727 |
728 | GUILayout.EndHorizontal();
729 | }
730 | else
731 | {
732 | // Refresh locks info
733 | GUILayout.BeginHorizontal();
734 | if (GitLocks.CurrentlyRefreshing)
735 | {
736 | GUILayout.Label("Last refresh time : currently refreshing...");
737 | }
738 | else
739 | {
740 | GUILayout.Label("Last refresh time : " + GitLocks.LastRefresh.ToShortTimeString());
741 | }
742 |
743 | EditorGUI.BeginDisabledGroup(true);
744 | bool autoRefresh = EditorPrefs.GetBool("gitLocksAutoRefreshLocks", true);
745 | if (autoRefresh)
746 | {
747 | int refreshLocksInterval = EditorPrefs.GetInt("gitLocksRefreshLocksInterval");
748 | string refreshIntervalStr = "Auto refresh every " + refreshLocksInterval.ToString() + " " + (refreshLocksInterval > 1 ? "minutes" : "minute");
749 | GUILayout.Label(refreshIntervalStr);
750 | }
751 | else
752 | {
753 | GUILayout.Label("Manual refresh only");
754 | }
755 |
756 | EditorGUI.EndDisabledGroup();
757 | if (GUILayout.Button("Setup"))
758 | {
759 | SettingsService.OpenUserPreferences("Preferences/Git Locks");
760 | }
761 |
762 | GUILayout.EndHorizontal();
763 |
764 | // Refresh button
765 | if (GUILayout.Button("Refresh Git LFS locks"))
766 | {
767 | GitLocks.RefreshLocks();
768 | }
769 |
770 | DrawUILine(Color.grey, 2, 20);
771 |
772 | // Lock action
773 | GUILayout.BeginHorizontal();
774 | EditorGUILayout.LabelField("Lock an asset :");
775 | this.objectToLock = EditorGUILayout.ObjectField(this.objectToLock, typeof(UnityEngine.Object), true);
776 | if (GUILayout.Button("Lock"))
777 | {
778 | string path = AssetDatabase.GetAssetPath(this.objectToLock);
779 |
780 | if (path == null || path == string.Empty)
781 | {
782 | path = GitLocks.GetAssetPathFromPrefabGameObject(this.objectToLock.GetInstanceID());
783 | }
784 |
785 | GitLocks.LockFile(path);
786 | this.objectToLock = null;
787 | GitLocks.RefreshLocks();
788 | }
789 |
790 | GUILayout.EndHorizontal();
791 |
792 | GUILayout.Space(5);
793 |
794 | // My locks
795 | GUILayout.BeginHorizontal();
796 | EditorGUILayout.LabelField("My locks", EditorStyles.boldLabel);
797 |
798 | GUILayout.FlexibleSpace();
799 |
800 | bool allSelected = false;
801 | if (selectedLocks != null && GitLocks.GetMyLocks() != null && selectedLocks.Count > 0)
802 | {
803 | allSelected = selectedLocks.Count == GitLocks.GetMyLocks().Count;
804 | }
805 | bool allSelectedCheckbox = GUILayout.Toggle(allSelected, "All");
806 | if (selectedLocks != null && allSelectedCheckbox && !allSelected)
807 | {
808 | selectedLocks.Clear();
809 | selectedLocks.AddRange(GitLocks.GetMyLocks());
810 | }
811 | else if (selectedLocks != null && !allSelectedCheckbox && allSelected)
812 | {
813 | selectedLocks.Clear();
814 | }
815 |
816 | GUILayout.Space(10);
817 |
818 | if (GUILayout.Button("Unlock selected", GUILayout.Width(120)))
819 | {
820 | GitLocks.UnlockMultipleLocks(selectedLocks);
821 | selectedLocks.Clear();
822 | }
823 |
824 | GUILayout.EndHorizontal();
825 |
826 | if (GitLocks.LockedObjectsCache != null && GitLocks.LockedObjectsCache.Count > 0)
827 | {
828 | // Compute min height for "My locks"
829 | int numOfLines = Mathf.Min(EditorPrefs.GetInt("numOfMyLocksDisplayed", 5), GitLocks.GetMyLocks().Count);
830 | float scrollViewHeight = ((float)numOfLines + 0.5f) * 21.0f; // Line height hardcoded for now, +0.5 is used to make sure there's something crossing the float line for clarity
831 |
832 | this.scrollPosMine = EditorGUILayout.BeginScrollView(this.scrollPosMine, GUILayout.MinHeight(scrollViewHeight));
833 | foreach (GitLocksObject lo in GitLocks.GetMyLocks())
834 | {
835 | if (true || lo != null)
836 | {
837 | DrawLockedObjectLine(lo, true);
838 | }
839 | }
840 |
841 | EditorGUILayout.EndScrollView();
842 | }
843 |
844 | DrawUILine(Color.grey, 2, 20);
845 |
846 | // Other locks
847 | GUILayout.BeginHorizontal();
848 | EditorGUILayout.LabelField("Other locks", EditorStyles.boldLabel);
849 | GUILayout.EndHorizontal();
850 | if (GitLocks.LockedObjectsCache != null && GitLocks.LockedObjectsCache.Count > 0)
851 | {
852 | this.scrollPosOthers = EditorGUILayout.BeginScrollView(this.scrollPosOthers);
853 | foreach (GitLocksObject lo in GitLocks.GetOtherLocks())
854 | {
855 | if (true || lo != null)
856 | {
857 | DrawLockedObjectLine(lo);
858 | }
859 | }
860 |
861 | EditorGUILayout.EndScrollView();
862 | }
863 | }
864 | }
865 | }
866 | }
867 |
--------------------------------------------------------------------------------
/Editor/Scripts/GitLocksDisplay.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f9b21c94fad686a488c68535e78cef95
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/Scripts/GitLocksFileModificationWarning.cs:
--------------------------------------------------------------------------------
1 | // All rights reserved.
2 |
3 | using UnityEditor;
4 |
5 | public class GitLocksFileModificationWarning : UnityEditor.AssetModificationProcessor
6 | {
7 | private static string[] OnWillSaveAssets(string[] paths)
8 | {
9 | if (!GitLocks.IsEnabled())
10 | {
11 | return paths; // Early return if the whole tool is disabled
12 | }
13 |
14 | // Refresh the uncommited cache as it probably has changed
15 | GitLocks.SetUncommitedCacheDirty();
16 |
17 | // check if we should display a warning for these files
18 | if (GitLocks.GetDisplayLocksConflictWarning())
19 | {
20 | foreach (string path in paths)
21 | {
22 | GitLocksObject lo = GitLocks.GetObjectInLockedCache(path);
23 | if (lo != null && !lo.IsMine() && !GitLocks.IsFileInConflictIgnoreList(path))
24 | {
25 | EditorUtility.DisplayDialog("Warning", "The following file you just saved is currently locked by " + lo.owner.name + ", you will not be able to push it.\n" + lo.path, "OK");
26 | GitLocks.AddFileToConflictWarningIgnoreList(path);
27 | }
28 | }
29 | }
30 |
31 | return paths;
32 | }
33 | }
--------------------------------------------------------------------------------
/Editor/Scripts/GitLocksFileModificationWarning.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4fd98a686d11d484dbad19a9d8da2672
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/Scripts/GitLocksObject.cs:
--------------------------------------------------------------------------------
1 | // All rights reserved.
2 |
3 | using System;
4 | using System.Runtime.Serialization;
5 | using UnityEditor;
6 |
7 | [System.Serializable]
8 | public class GitLocksObject
9 | {
10 | public int id { get; set; }
11 |
12 | public string path { get; set; }
13 |
14 | public LockedObjectOwner owner { get; set; }
15 |
16 | public string locked_at { get; set; }
17 |
18 | public UnityEngine.Object objectRef = null;
19 |
20 | public UnityEngine.Object GetObjectReference()
21 | {
22 | if (this.objectRef != null)
23 | {
24 | return this.objectRef;
25 | }
26 | else
27 | {
28 | this.objectRef = AssetDatabase.LoadMainAssetAtPath(path);
29 | return this.objectRef;
30 | }
31 | }
32 |
33 | public bool IsMine()
34 | {
35 | return this.owner.name == GitLocks.GetGitUsername();
36 | }
37 |
38 | public string GetLockDateTimeString()
39 | {
40 | DateTime dt = DateTime.Parse(locked_at);
41 | string r = dt.ToShortDateString() + " - " + dt.ToShortTimeString();
42 | return r;
43 | }
44 |
45 | [OnDeserialized]
46 | internal void OnDeserializedMethod(StreamingContext context)
47 | {
48 | path = FindRelativePath("Assets");
49 | }
50 |
51 | private string FindRelativePath(string relativeFolder)
52 | {
53 | int index = path.IndexOf(relativeFolder, StringComparison.OrdinalIgnoreCase);
54 | if (index != -1) return path.Substring(index);
55 | else return path;
56 | }
57 |
58 | }
59 |
60 | public class LockedObjectOwner
61 | {
62 | public string name;
63 | }
--------------------------------------------------------------------------------
/Editor/Scripts/GitLocksObject.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 9fd381c66f64f8d419cba9fe8af2d90c
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/Scripts/GitLocksPreferences.cs:
--------------------------------------------------------------------------------
1 | // All rights reserved.
2 |
3 | using UnityEditor;
4 | using UnityEngine;
5 |
6 | public class GitLocksPreferences : SettingsProvider
7 | {
8 | public GitLocksPreferences(string path, SettingsScope scope = SettingsScope.User) : base(path, scope)
9 | {
10 | }
11 |
12 | [SettingsProvider]
13 | public static SettingsProvider RegisterSettingsProvider()
14 | {
15 | var provider = new GitLocksPreferences("Preferences/Git Locks", SettingsScope.User);
16 |
17 | // Automatically extract all keywords from the Styles.
18 | provider.keywords = new string[]
19 | {
20 | "Enable Git LFS lock tool",
21 | "Git host username",
22 | "Auto refresh locks",
23 | "Refresh locks interval (minutes)",
24 | "Display locks conflict warning",
25 | "Warn if I still own locks on quit",
26 | "Minimum number of my locks to show"
27 | };
28 | return provider;
29 | }
30 |
31 | public override void OnGUI(string searchContext)
32 | {
33 | EditorGUILayout.Space();
34 | EditorGUILayout.LabelField("General settings", EditorStyles.boldLabel);
35 |
36 | float previousLabelWidth = EditorGUIUtility.labelWidth;
37 | EditorGUI.indentLevel++;
38 |
39 | GUILayout.Space(5);
40 |
41 | EditorGUI.BeginChangeCheck();
42 | bool enabled = EditorGUILayout.ToggleLeft(new GUIContent("Enable Git LFS locks tool"), EditorPrefs.GetBool("gitLocksEnabled"), GUILayout.Width(180));
43 | if (EditorGUI.EndChangeCheck())
44 | {
45 | EditorPrefs.SetBool("gitLocksEnabled", enabled);
46 | }
47 |
48 | if (enabled)
49 | {
50 | GUILayout.Space(5);
51 |
52 | EditorGUI.BeginChangeCheck();
53 | EditorGUILayout.BeginHorizontal();
54 | string username = EditorGUILayout.TextField(new GUIContent("Git host username"), EditorPrefs.GetString("gitLocksHostUsername"));
55 | if (EditorGUI.EndChangeCheck())
56 | {
57 | EditorPrefs.SetString("gitLocksHostUsername", username);
58 | }
59 |
60 | if (GUILayout.Button("Go on github"))
61 | {
62 | UnityEngine.Application.OpenURL("https://github.com/");
63 | }
64 |
65 | EditorGUILayout.EndHorizontal();
66 |
67 | GUILayout.Space(5);
68 |
69 | EditorGUI.BeginChangeCheck();
70 | int maxFilesNumPerRequest = EditorGUILayout.IntField(new GUIContent("Max number of files grouped per request"), EditorPrefs.GetInt("gitLocksMaxFilesNumPerRequest"));
71 | if (EditorGUI.EndChangeCheck())
72 | {
73 | EditorPrefs.SetInt("gitLocksMaxFilesNumPerRequest", maxFilesNumPerRequest);
74 | }
75 |
76 | GUILayout.Space(5);
77 |
78 | EditorGUILayout.BeginHorizontal();
79 | EditorGUI.BeginChangeCheck();
80 | bool autoRefresh = EditorGUILayout.ToggleLeft(new GUIContent("Auto refresh locks"), EditorPrefs.GetBool("gitLocksAutoRefreshLocks"), GUILayout.Width(135));
81 | if (EditorGUI.EndChangeCheck())
82 | {
83 | EditorPrefs.SetBool("gitLocksAutoRefreshLocks", autoRefresh);
84 | }
85 |
86 | EditorGUI.BeginDisabledGroup(!autoRefresh);
87 | EditorGUILayout.LabelField("every", GUILayout.Width(50));
88 | EditorGUI.BeginChangeCheck();
89 | int interval = EditorGUILayout.IntField(EditorPrefs.GetInt("gitLocksRefreshLocksInterval"), GUILayout.Width(40));
90 | if (EditorGUI.EndChangeCheck())
91 | {
92 | EditorPrefs.SetInt("gitLocksRefreshLocksInterval", interval);
93 | }
94 |
95 | EditorGUILayout.LabelField("minutes", GUILayout.Width(70));
96 |
97 | EditorGUI.EndDisabledGroup();
98 |
99 | EditorGUILayout.EndHorizontal();
100 |
101 | // Notifications
102 | EditorGUI.indentLevel--;
103 | EditorGUILayout.Space(10);
104 | EditorGUILayout.LabelField("Notifications", EditorStyles.boldLabel);
105 | previousLabelWidth = EditorGUIUtility.labelWidth;
106 | EditorGUI.indentLevel++;
107 |
108 | EditorGUI.BeginChangeCheck();
109 | bool displayLocksConflictWarning = EditorGUILayout.ToggleLeft(new GUIContent("Warn if a file I modified is already locked"), EditorPrefs.GetBool("displayLocksConflictWarning"));
110 | if (EditorGUI.EndChangeCheck())
111 | {
112 | EditorPrefs.SetBool("displayLocksConflictWarning", displayLocksConflictWarning);
113 | }
114 |
115 | EditorGUI.BeginChangeCheck();
116 | bool warnIfIStillOwnLocksOnQuit = EditorGUILayout.ToggleLeft(new GUIContent("Warn if I still own locks on quit"), EditorPrefs.GetBool("warnIfIStillOwnLocksOnQuit"));
117 | if (EditorGUI.EndChangeCheck())
118 | {
119 | EditorPrefs.SetBool("warnIfIStillOwnLocksOnQuit", warnIfIStillOwnLocksOnQuit);
120 | }
121 |
122 | EditorGUI.BeginChangeCheck();
123 | bool warnIfFileHasBeenModifiedOnServer = EditorGUILayout.ToggleLeft(new GUIContent("Warn if the file has already been modified on server when locking"), EditorPrefs.GetBool("warnIfFileHasBeenModifiedOnServer"));
124 | if (EditorGUI.EndChangeCheck())
125 | {
126 | EditorPrefs.SetBool("warnIfFileHasBeenModifiedOnServer", warnIfFileHasBeenModifiedOnServer);
127 | }
128 |
129 | if (EditorPrefs.GetBool("warnIfFileHasBeenModifiedOnServer"))
130 | {
131 | EditorGUI.BeginChangeCheck();
132 | string branchesToCheck = EditorGUILayout.TextField(new GUIContent("Other branches to check (',' separated)"), EditorPrefs.GetString("gitLocksBranchesToCheck"));
133 | if (EditorGUI.EndChangeCheck())
134 | {
135 | EditorPrefs.SetString("gitLocksBranchesToCheck", branchesToCheck);
136 | }
137 | }
138 |
139 | EditorGUI.BeginChangeCheck();
140 | bool notifyNewLocks = EditorGUILayout.ToggleLeft(new GUIContent("Notify when there are new locks and when launching Unity"), EditorPrefs.GetBool("notifyNewLocks"));
141 | if (EditorGUI.EndChangeCheck())
142 | {
143 | EditorPrefs.SetBool("notifyNewLocks", notifyNewLocks);
144 | }
145 |
146 | // Display
147 | EditorGUI.indentLevel--;
148 | EditorGUILayout.Space(10);
149 | EditorGUILayout.LabelField("Display", EditorStyles.boldLabel);
150 | previousLabelWidth = EditorGUIUtility.labelWidth;
151 | EditorGUI.indentLevel++;
152 |
153 | EditorGUI.BeginChangeCheck();
154 | int numOfMyLocksDisplayed = EditorGUILayout.IntField(new GUIContent("Number of my locks displayed"), EditorPrefs.GetInt("numOfMyLocksDisplayed"));
155 | if (EditorGUI.EndChangeCheck())
156 | {
157 | EditorPrefs.SetInt("numOfMyLocksDisplayed", numOfMyLocksDisplayed);
158 | }
159 |
160 | EditorGUI.BeginChangeCheck();
161 | bool colorblindMode = EditorGUILayout.ToggleLeft(new GUIContent("Colorblind mode"), EditorPrefs.GetBool("gitLocksColorblindMode"));
162 | if (EditorGUI.EndChangeCheck())
163 | {
164 | EditorPrefs.SetBool("gitLocksColorblindMode", colorblindMode);
165 | GitLocksDisplay.RefreshLockIcons();
166 | }
167 |
168 | EditorGUI.BeginChangeCheck();
169 | bool debugMode = EditorGUILayout.ToggleLeft(new GUIContent("Show debug logs"), EditorPrefs.GetBool("gitLocksDebugMode"));
170 | if (EditorGUI.EndChangeCheck())
171 | {
172 | EditorPrefs.SetBool("gitLocksDebugMode", debugMode);
173 | }
174 |
175 | EditorGUI.BeginChangeCheck();
176 | bool showForceButtons = EditorGUILayout.ToggleLeft(new GUIContent("Show Force buttons"), EditorPrefs.GetBool("gitLocksShowForceButtons"));
177 | if (EditorGUI.EndChangeCheck())
178 | {
179 | EditorPrefs.SetBool("gitLocksShowForceButtons", showForceButtons);
180 | }
181 | }
182 |
183 | GUILayout.Space(10);
184 |
185 | EditorGUI.indentLevel--;
186 | EditorGUIUtility.labelWidth = previousLabelWidth;
187 |
188 | // Misc
189 | EditorGUILayout.LabelField("Misc", EditorStyles.boldLabel);
190 |
191 | EditorGUIUtility.labelWidth = 250;
192 | EditorGUI.indentLevel++;
193 |
194 | EditorGUILayout.BeginHorizontal();
195 |
196 | EditorGUI.BeginChangeCheck();
197 | bool showHistoryInBrowser = EditorGUILayout.ToggleLeft(new GUIContent("Show file history in browser"), EditorPrefs.GetBool("gitLocksShowHistoryInBrowser"), GUILayout.Width(195));
198 | if (EditorGUI.EndChangeCheck())
199 | {
200 | EditorPrefs.SetBool("gitLocksShowHistoryInBrowser", showHistoryInBrowser);
201 | }
202 |
203 | EditorGUI.BeginDisabledGroup(!showHistoryInBrowser);
204 | EditorGUI.BeginChangeCheck();
205 | string url = EditorGUILayout.TextField(EditorPrefs.GetString("gitLocksShowHistoryInBrowserUrl"));
206 | if (EditorGUI.EndChangeCheck())
207 | {
208 | EditorPrefs.SetString("gitLocksShowHistoryInBrowserUrl", url);
209 | }
210 | EditorGUI.EndDisabledGroup();
211 |
212 | EditorGUILayout.EndHorizontal();
213 |
214 | GUILayout.Space(10);
215 |
216 | EditorGUI.indentLevel--;
217 | EditorGUIUtility.labelWidth = previousLabelWidth;
218 |
219 | // Git config and troubleshooting
220 | EditorGUILayout.LabelField("Git config and troubleshooting", EditorStyles.boldLabel);
221 |
222 | EditorGUIUtility.labelWidth = 250;
223 | EditorGUI.indentLevel++;
224 |
225 | EditorGUILayout.Space();
226 |
227 | EditorGUILayout.LabelField(new GUIContent(GitLocks.GetGitVersion()), GUILayout.Height(25));
228 | if (GitLocks.IsGitOutdated())
229 | {
230 | EditorGUILayout.LabelField(new GUIContent("Your git version seems outdated (2.30.0 minimum), you may need to update it and then setup the Credentials Manager for the authentication to work properly"), EditorStyles.wordWrappedLabel);
231 | if (GUILayout.Button("Update Git for Windows"))
232 | {
233 | GitLocks.ExecuteProcessTerminal("git", "update-git-for-windows", true);
234 | }
235 | }
236 |
237 | if (GUILayout.Button("Setup credentials manager (when using HTTPS)"))
238 | {
239 | GitLocks.ExecuteProcessTerminalWithConsole("git", "config --local credential.helper manager");
240 | }
241 |
242 | EditorGUI.indentLevel--;
243 | }
244 | }
--------------------------------------------------------------------------------
/Editor/Scripts/GitLocksPreferences.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 6984f267f083aa54f8c7d62ac77a5eba
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/Textures.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d95e68e708191a94ead1649dc333a3d2
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Editor/Textures/greenLock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TomDuchene/unity-git-locks/80a2d4ad8b45b5b88b3bc77a79f96b5670dd8f18/Editor/Textures/greenLock.png
--------------------------------------------------------------------------------
/Editor/Textures/greenLock.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: bea65576b35b3504db4f2476e3d41487
3 | TextureImporter:
4 | internalIDToNameTable: []
5 | externalObjects: {}
6 | serializedVersion: 11
7 | mipmaps:
8 | mipMapMode: 0
9 | enableMipMap: 1
10 | sRGBTexture: 1
11 | linearTexture: 0
12 | fadeOut: 0
13 | borderMipMap: 0
14 | mipMapsPreserveCoverage: 0
15 | alphaTestReferenceValue: 0.5
16 | mipMapFadeDistanceStart: 1
17 | mipMapFadeDistanceEnd: 3
18 | bumpmap:
19 | convertToNormalMap: 0
20 | externalNormalMap: 0
21 | heightScale: 0.25
22 | normalMapFilter: 0
23 | isReadable: 0
24 | streamingMipmaps: 0
25 | streamingMipmapsPriority: 0
26 | grayScaleToAlpha: 0
27 | generateCubemap: 6
28 | cubemapConvolution: 0
29 | seamlessCubemap: 0
30 | textureFormat: 1
31 | maxTextureSize: 2048
32 | textureSettings:
33 | serializedVersion: 2
34 | filterMode: 1
35 | aniso: 1
36 | mipBias: 0
37 | wrapU: 0
38 | wrapV: 0
39 | wrapW: 0
40 | nPOTScale: 1
41 | lightmap: 0
42 | compressionQuality: 50
43 | spriteMode: 0
44 | spriteExtrude: 1
45 | spriteMeshType: 1
46 | alignment: 0
47 | spritePivot: {x: 0.5, y: 0.5}
48 | spritePixelsToUnits: 100
49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
50 | spriteGenerateFallbackPhysicsShape: 1
51 | alphaUsage: 1
52 | alphaIsTransparency: 0
53 | spriteTessellationDetail: -1
54 | textureType: 0
55 | textureShape: 1
56 | singleChannelComponent: 0
57 | maxTextureSizeSet: 0
58 | compressionQualitySet: 0
59 | textureFormatSet: 0
60 | applyGammaDecoding: 0
61 | platformSettings:
62 | - serializedVersion: 3
63 | buildTarget: DefaultTexturePlatform
64 | maxTextureSize: 2048
65 | resizeAlgorithm: 0
66 | textureFormat: -1
67 | textureCompression: 1
68 | compressionQuality: 50
69 | crunchedCompression: 0
70 | allowsAlphaSplitting: 0
71 | overridden: 0
72 | androidETC2FallbackOverride: 0
73 | forceMaximumCompressionQuality_BC6H_BC7: 0
74 | spriteSheet:
75 | serializedVersion: 2
76 | sprites: []
77 | outline: []
78 | physicsShape: []
79 | bones: []
80 | spriteID:
81 | internalID: 0
82 | vertices: []
83 | indices:
84 | edges: []
85 | weights: []
86 | secondaryTextures: []
87 | spritePackingTag:
88 | pSDRemoveMatte: 0
89 | pSDShowRemoveMatteOption: 0
90 | userData:
91 | assetBundleName:
92 | assetBundleVariant:
93 |
--------------------------------------------------------------------------------
/Editor/Textures/greenLock_cb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TomDuchene/unity-git-locks/80a2d4ad8b45b5b88b3bc77a79f96b5670dd8f18/Editor/Textures/greenLock_cb.png
--------------------------------------------------------------------------------
/Editor/Textures/greenLock_cb.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 8e9097f0e4f64c74ba9d9c417f9f45fe
3 | TextureImporter:
4 | internalIDToNameTable: []
5 | externalObjects: {}
6 | serializedVersion: 11
7 | mipmaps:
8 | mipMapMode: 0
9 | enableMipMap: 1
10 | sRGBTexture: 1
11 | linearTexture: 0
12 | fadeOut: 0
13 | borderMipMap: 0
14 | mipMapsPreserveCoverage: 0
15 | alphaTestReferenceValue: 0.5
16 | mipMapFadeDistanceStart: 1
17 | mipMapFadeDistanceEnd: 3
18 | bumpmap:
19 | convertToNormalMap: 0
20 | externalNormalMap: 0
21 | heightScale: 0.25
22 | normalMapFilter: 0
23 | isReadable: 0
24 | streamingMipmaps: 0
25 | streamingMipmapsPriority: 0
26 | grayScaleToAlpha: 0
27 | generateCubemap: 6
28 | cubemapConvolution: 0
29 | seamlessCubemap: 0
30 | textureFormat: 1
31 | maxTextureSize: 2048
32 | textureSettings:
33 | serializedVersion: 2
34 | filterMode: 1
35 | aniso: 1
36 | mipBias: 0
37 | wrapU: 0
38 | wrapV: 0
39 | wrapW: 0
40 | nPOTScale: 1
41 | lightmap: 0
42 | compressionQuality: 50
43 | spriteMode: 0
44 | spriteExtrude: 1
45 | spriteMeshType: 1
46 | alignment: 0
47 | spritePivot: {x: 0.5, y: 0.5}
48 | spritePixelsToUnits: 100
49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
50 | spriteGenerateFallbackPhysicsShape: 1
51 | alphaUsage: 1
52 | alphaIsTransparency: 0
53 | spriteTessellationDetail: -1
54 | textureType: 0
55 | textureShape: 1
56 | singleChannelComponent: 0
57 | maxTextureSizeSet: 0
58 | compressionQualitySet: 0
59 | textureFormatSet: 0
60 | applyGammaDecoding: 0
61 | platformSettings:
62 | - serializedVersion: 3
63 | buildTarget: DefaultTexturePlatform
64 | maxTextureSize: 2048
65 | resizeAlgorithm: 0
66 | textureFormat: -1
67 | textureCompression: 1
68 | compressionQuality: 50
69 | crunchedCompression: 0
70 | allowsAlphaSplitting: 0
71 | overridden: 0
72 | androidETC2FallbackOverride: 0
73 | forceMaximumCompressionQuality_BC6H_BC7: 0
74 | spriteSheet:
75 | serializedVersion: 2
76 | sprites: []
77 | outline: []
78 | physicsShape: []
79 | bones: []
80 | spriteID:
81 | internalID: 0
82 | vertices: []
83 | indices:
84 | edges: []
85 | weights: []
86 | secondaryTextures: []
87 | spritePackingTag:
88 | pSDRemoveMatte: 0
89 | pSDShowRemoveMatteOption: 0
90 | userData:
91 | assetBundleName:
92 | assetBundleVariant:
93 |
--------------------------------------------------------------------------------
/Editor/Textures/mixedLock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TomDuchene/unity-git-locks/80a2d4ad8b45b5b88b3bc77a79f96b5670dd8f18/Editor/Textures/mixedLock.png
--------------------------------------------------------------------------------
/Editor/Textures/mixedLock.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f092de50f1c58904f9b74824046ba6e2
3 | TextureImporter:
4 | internalIDToNameTable: []
5 | externalObjects: {}
6 | serializedVersion: 11
7 | mipmaps:
8 | mipMapMode: 0
9 | enableMipMap: 1
10 | sRGBTexture: 1
11 | linearTexture: 0
12 | fadeOut: 0
13 | borderMipMap: 0
14 | mipMapsPreserveCoverage: 0
15 | alphaTestReferenceValue: 0.5
16 | mipMapFadeDistanceStart: 1
17 | mipMapFadeDistanceEnd: 3
18 | bumpmap:
19 | convertToNormalMap: 0
20 | externalNormalMap: 0
21 | heightScale: 0.25
22 | normalMapFilter: 0
23 | isReadable: 0
24 | streamingMipmaps: 0
25 | streamingMipmapsPriority: 0
26 | grayScaleToAlpha: 0
27 | generateCubemap: 6
28 | cubemapConvolution: 0
29 | seamlessCubemap: 0
30 | textureFormat: 1
31 | maxTextureSize: 2048
32 | textureSettings:
33 | serializedVersion: 2
34 | filterMode: 1
35 | aniso: 1
36 | mipBias: 0
37 | wrapU: 0
38 | wrapV: 0
39 | wrapW: 0
40 | nPOTScale: 1
41 | lightmap: 0
42 | compressionQuality: 50
43 | spriteMode: 0
44 | spriteExtrude: 1
45 | spriteMeshType: 1
46 | alignment: 0
47 | spritePivot: {x: 0.5, y: 0.5}
48 | spritePixelsToUnits: 100
49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
50 | spriteGenerateFallbackPhysicsShape: 1
51 | alphaUsage: 1
52 | alphaIsTransparency: 0
53 | spriteTessellationDetail: -1
54 | textureType: 0
55 | textureShape: 1
56 | singleChannelComponent: 0
57 | maxTextureSizeSet: 0
58 | compressionQualitySet: 0
59 | textureFormatSet: 0
60 | applyGammaDecoding: 0
61 | platformSettings:
62 | - serializedVersion: 3
63 | buildTarget: DefaultTexturePlatform
64 | maxTextureSize: 2048
65 | resizeAlgorithm: 0
66 | textureFormat: -1
67 | textureCompression: 1
68 | compressionQuality: 50
69 | crunchedCompression: 0
70 | allowsAlphaSplitting: 0
71 | overridden: 0
72 | androidETC2FallbackOverride: 0
73 | forceMaximumCompressionQuality_BC6H_BC7: 0
74 | spriteSheet:
75 | serializedVersion: 2
76 | sprites: []
77 | outline: []
78 | physicsShape: []
79 | bones: []
80 | spriteID:
81 | internalID: 0
82 | vertices: []
83 | indices:
84 | edges: []
85 | weights: []
86 | secondaryTextures: []
87 | spritePackingTag:
88 | pSDRemoveMatte: 0
89 | pSDShowRemoveMatteOption: 0
90 | userData:
91 | assetBundleName:
92 | assetBundleVariant:
93 |
--------------------------------------------------------------------------------
/Editor/Textures/mixedLock_cb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TomDuchene/unity-git-locks/80a2d4ad8b45b5b88b3bc77a79f96b5670dd8f18/Editor/Textures/mixedLock_cb.png
--------------------------------------------------------------------------------
/Editor/Textures/mixedLock_cb.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1e0a33f582e26a441a7188b9b24ebcd2
3 | TextureImporter:
4 | internalIDToNameTable: []
5 | externalObjects: {}
6 | serializedVersion: 11
7 | mipmaps:
8 | mipMapMode: 0
9 | enableMipMap: 1
10 | sRGBTexture: 1
11 | linearTexture: 0
12 | fadeOut: 0
13 | borderMipMap: 0
14 | mipMapsPreserveCoverage: 0
15 | alphaTestReferenceValue: 0.5
16 | mipMapFadeDistanceStart: 1
17 | mipMapFadeDistanceEnd: 3
18 | bumpmap:
19 | convertToNormalMap: 0
20 | externalNormalMap: 0
21 | heightScale: 0.25
22 | normalMapFilter: 0
23 | isReadable: 0
24 | streamingMipmaps: 0
25 | streamingMipmapsPriority: 0
26 | grayScaleToAlpha: 0
27 | generateCubemap: 6
28 | cubemapConvolution: 0
29 | seamlessCubemap: 0
30 | textureFormat: 1
31 | maxTextureSize: 2048
32 | textureSettings:
33 | serializedVersion: 2
34 | filterMode: 1
35 | aniso: 1
36 | mipBias: 0
37 | wrapU: 0
38 | wrapV: 0
39 | wrapW: 0
40 | nPOTScale: 1
41 | lightmap: 0
42 | compressionQuality: 50
43 | spriteMode: 0
44 | spriteExtrude: 1
45 | spriteMeshType: 1
46 | alignment: 0
47 | spritePivot: {x: 0.5, y: 0.5}
48 | spritePixelsToUnits: 100
49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
50 | spriteGenerateFallbackPhysicsShape: 1
51 | alphaUsage: 1
52 | alphaIsTransparency: 0
53 | spriteTessellationDetail: -1
54 | textureType: 0
55 | textureShape: 1
56 | singleChannelComponent: 0
57 | maxTextureSizeSet: 0
58 | compressionQualitySet: 0
59 | textureFormatSet: 0
60 | applyGammaDecoding: 0
61 | platformSettings:
62 | - serializedVersion: 3
63 | buildTarget: DefaultTexturePlatform
64 | maxTextureSize: 2048
65 | resizeAlgorithm: 0
66 | textureFormat: -1
67 | textureCompression: 1
68 | compressionQuality: 50
69 | crunchedCompression: 0
70 | allowsAlphaSplitting: 0
71 | overridden: 0
72 | androidETC2FallbackOverride: 0
73 | forceMaximumCompressionQuality_BC6H_BC7: 0
74 | spriteSheet:
75 | serializedVersion: 2
76 | sprites: []
77 | outline: []
78 | physicsShape: []
79 | bones: []
80 | spriteID:
81 | internalID: 0
82 | vertices: []
83 | indices:
84 | edges: []
85 | weights: []
86 | secondaryTextures: []
87 | spritePackingTag:
88 | pSDRemoveMatte: 0
89 | pSDShowRemoveMatteOption: 0
90 | userData:
91 | assetBundleName:
92 | assetBundleVariant:
93 |
--------------------------------------------------------------------------------
/Editor/Textures/orangeLock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TomDuchene/unity-git-locks/80a2d4ad8b45b5b88b3bc77a79f96b5670dd8f18/Editor/Textures/orangeLock.png
--------------------------------------------------------------------------------
/Editor/Textures/orangeLock.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4a3e3ac4b67ff4d49a5892b5739c1a00
3 | TextureImporter:
4 | internalIDToNameTable: []
5 | externalObjects: {}
6 | serializedVersion: 11
7 | mipmaps:
8 | mipMapMode: 0
9 | enableMipMap: 1
10 | sRGBTexture: 1
11 | linearTexture: 0
12 | fadeOut: 0
13 | borderMipMap: 0
14 | mipMapsPreserveCoverage: 0
15 | alphaTestReferenceValue: 0.5
16 | mipMapFadeDistanceStart: 1
17 | mipMapFadeDistanceEnd: 3
18 | bumpmap:
19 | convertToNormalMap: 0
20 | externalNormalMap: 0
21 | heightScale: 0.25
22 | normalMapFilter: 0
23 | isReadable: 0
24 | streamingMipmaps: 0
25 | streamingMipmapsPriority: 0
26 | grayScaleToAlpha: 0
27 | generateCubemap: 6
28 | cubemapConvolution: 0
29 | seamlessCubemap: 0
30 | textureFormat: 1
31 | maxTextureSize: 2048
32 | textureSettings:
33 | serializedVersion: 2
34 | filterMode: 1
35 | aniso: 1
36 | mipBias: 0
37 | wrapU: 0
38 | wrapV: 0
39 | wrapW: 0
40 | nPOTScale: 1
41 | lightmap: 0
42 | compressionQuality: 50
43 | spriteMode: 0
44 | spriteExtrude: 1
45 | spriteMeshType: 1
46 | alignment: 0
47 | spritePivot: {x: 0.5, y: 0.5}
48 | spritePixelsToUnits: 100
49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
50 | spriteGenerateFallbackPhysicsShape: 1
51 | alphaUsage: 1
52 | alphaIsTransparency: 0
53 | spriteTessellationDetail: -1
54 | textureType: 0
55 | textureShape: 1
56 | singleChannelComponent: 0
57 | maxTextureSizeSet: 0
58 | compressionQualitySet: 0
59 | textureFormatSet: 0
60 | applyGammaDecoding: 0
61 | platformSettings:
62 | - serializedVersion: 3
63 | buildTarget: DefaultTexturePlatform
64 | maxTextureSize: 2048
65 | resizeAlgorithm: 0
66 | textureFormat: -1
67 | textureCompression: 1
68 | compressionQuality: 50
69 | crunchedCompression: 0
70 | allowsAlphaSplitting: 0
71 | overridden: 0
72 | androidETC2FallbackOverride: 0
73 | forceMaximumCompressionQuality_BC6H_BC7: 0
74 | spriteSheet:
75 | serializedVersion: 2
76 | sprites: []
77 | outline: []
78 | physicsShape: []
79 | bones: []
80 | spriteID:
81 | internalID: 0
82 | vertices: []
83 | indices:
84 | edges: []
85 | weights: []
86 | secondaryTextures: []
87 | spritePackingTag:
88 | pSDRemoveMatte: 0
89 | pSDShowRemoveMatteOption: 0
90 | userData:
91 | assetBundleName:
92 | assetBundleVariant:
93 |
--------------------------------------------------------------------------------
/Editor/Textures/orangeLock_cb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TomDuchene/unity-git-locks/80a2d4ad8b45b5b88b3bc77a79f96b5670dd8f18/Editor/Textures/orangeLock_cb.png
--------------------------------------------------------------------------------
/Editor/Textures/orangeLock_cb.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 6d664e093d8cc704da052d22771bacaf
3 | TextureImporter:
4 | internalIDToNameTable: []
5 | externalObjects: {}
6 | serializedVersion: 11
7 | mipmaps:
8 | mipMapMode: 0
9 | enableMipMap: 1
10 | sRGBTexture: 1
11 | linearTexture: 0
12 | fadeOut: 0
13 | borderMipMap: 0
14 | mipMapsPreserveCoverage: 0
15 | alphaTestReferenceValue: 0.5
16 | mipMapFadeDistanceStart: 1
17 | mipMapFadeDistanceEnd: 3
18 | bumpmap:
19 | convertToNormalMap: 0
20 | externalNormalMap: 0
21 | heightScale: 0.25
22 | normalMapFilter: 0
23 | isReadable: 0
24 | streamingMipmaps: 0
25 | streamingMipmapsPriority: 0
26 | grayScaleToAlpha: 0
27 | generateCubemap: 6
28 | cubemapConvolution: 0
29 | seamlessCubemap: 0
30 | textureFormat: 1
31 | maxTextureSize: 2048
32 | textureSettings:
33 | serializedVersion: 2
34 | filterMode: 1
35 | aniso: 1
36 | mipBias: 0
37 | wrapU: 0
38 | wrapV: 0
39 | wrapW: 0
40 | nPOTScale: 1
41 | lightmap: 0
42 | compressionQuality: 50
43 | spriteMode: 0
44 | spriteExtrude: 1
45 | spriteMeshType: 1
46 | alignment: 0
47 | spritePivot: {x: 0.5, y: 0.5}
48 | spritePixelsToUnits: 100
49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
50 | spriteGenerateFallbackPhysicsShape: 1
51 | alphaUsage: 1
52 | alphaIsTransparency: 0
53 | spriteTessellationDetail: -1
54 | textureType: 0
55 | textureShape: 1
56 | singleChannelComponent: 0
57 | maxTextureSizeSet: 0
58 | compressionQualitySet: 0
59 | textureFormatSet: 0
60 | applyGammaDecoding: 0
61 | platformSettings:
62 | - serializedVersion: 3
63 | buildTarget: DefaultTexturePlatform
64 | maxTextureSize: 2048
65 | resizeAlgorithm: 0
66 | textureFormat: -1
67 | textureCompression: 1
68 | compressionQuality: 50
69 | crunchedCompression: 0
70 | allowsAlphaSplitting: 0
71 | overridden: 0
72 | androidETC2FallbackOverride: 0
73 | forceMaximumCompressionQuality_BC6H_BC7: 0
74 | spriteSheet:
75 | serializedVersion: 2
76 | sprites: []
77 | outline: []
78 | physicsShape: []
79 | bones: []
80 | spriteID:
81 | internalID: 0
82 | vertices: []
83 | indices:
84 | edges: []
85 | weights: []
86 | secondaryTextures: []
87 | spritePackingTag:
88 | pSDRemoveMatte: 0
89 | pSDShowRemoveMatteOption: 0
90 | userData:
91 | assetBundleName:
92 | assetBundleVariant:
93 |
--------------------------------------------------------------------------------
/Editor/Textures/redLock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TomDuchene/unity-git-locks/80a2d4ad8b45b5b88b3bc77a79f96b5670dd8f18/Editor/Textures/redLock.png
--------------------------------------------------------------------------------
/Editor/Textures/redLock.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a698f22e17519e3438bb2da7e5010ea3
3 | TextureImporter:
4 | internalIDToNameTable: []
5 | externalObjects: {}
6 | serializedVersion: 11
7 | mipmaps:
8 | mipMapMode: 0
9 | enableMipMap: 1
10 | sRGBTexture: 1
11 | linearTexture: 0
12 | fadeOut: 0
13 | borderMipMap: 0
14 | mipMapsPreserveCoverage: 0
15 | alphaTestReferenceValue: 0.5
16 | mipMapFadeDistanceStart: 1
17 | mipMapFadeDistanceEnd: 3
18 | bumpmap:
19 | convertToNormalMap: 0
20 | externalNormalMap: 0
21 | heightScale: 0.25
22 | normalMapFilter: 0
23 | isReadable: 0
24 | streamingMipmaps: 0
25 | streamingMipmapsPriority: 0
26 | grayScaleToAlpha: 0
27 | generateCubemap: 6
28 | cubemapConvolution: 0
29 | seamlessCubemap: 0
30 | textureFormat: 1
31 | maxTextureSize: 2048
32 | textureSettings:
33 | serializedVersion: 2
34 | filterMode: 1
35 | aniso: 1
36 | mipBias: 0
37 | wrapU: 0
38 | wrapV: 0
39 | wrapW: 0
40 | nPOTScale: 1
41 | lightmap: 0
42 | compressionQuality: 50
43 | spriteMode: 0
44 | spriteExtrude: 1
45 | spriteMeshType: 1
46 | alignment: 0
47 | spritePivot: {x: 0.5, y: 0.5}
48 | spritePixelsToUnits: 100
49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
50 | spriteGenerateFallbackPhysicsShape: 1
51 | alphaUsage: 1
52 | alphaIsTransparency: 0
53 | spriteTessellationDetail: -1
54 | textureType: 0
55 | textureShape: 1
56 | singleChannelComponent: 0
57 | maxTextureSizeSet: 0
58 | compressionQualitySet: 0
59 | textureFormatSet: 0
60 | applyGammaDecoding: 0
61 | platformSettings:
62 | - serializedVersion: 3
63 | buildTarget: DefaultTexturePlatform
64 | maxTextureSize: 2048
65 | resizeAlgorithm: 0
66 | textureFormat: -1
67 | textureCompression: 1
68 | compressionQuality: 50
69 | crunchedCompression: 0
70 | allowsAlphaSplitting: 0
71 | overridden: 0
72 | androidETC2FallbackOverride: 0
73 | forceMaximumCompressionQuality_BC6H_BC7: 0
74 | spriteSheet:
75 | serializedVersion: 2
76 | sprites: []
77 | outline: []
78 | physicsShape: []
79 | bones: []
80 | spriteID:
81 | internalID: 0
82 | vertices: []
83 | indices:
84 | edges: []
85 | weights: []
86 | secondaryTextures: []
87 | spritePackingTag:
88 | pSDRemoveMatte: 0
89 | pSDShowRemoveMatteOption: 0
90 | userData:
91 | assetBundleName:
92 | assetBundleVariant:
93 |
--------------------------------------------------------------------------------
/Editor/Textures/redLock_cb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TomDuchene/unity-git-locks/80a2d4ad8b45b5b88b3bc77a79f96b5670dd8f18/Editor/Textures/redLock_cb.png
--------------------------------------------------------------------------------
/Editor/Textures/redLock_cb.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f13b7a145a92a164990a2d8e8e01400f
3 | TextureImporter:
4 | internalIDToNameTable: []
5 | externalObjects: {}
6 | serializedVersion: 11
7 | mipmaps:
8 | mipMapMode: 0
9 | enableMipMap: 1
10 | sRGBTexture: 1
11 | linearTexture: 0
12 | fadeOut: 0
13 | borderMipMap: 0
14 | mipMapsPreserveCoverage: 0
15 | alphaTestReferenceValue: 0.5
16 | mipMapFadeDistanceStart: 1
17 | mipMapFadeDistanceEnd: 3
18 | bumpmap:
19 | convertToNormalMap: 0
20 | externalNormalMap: 0
21 | heightScale: 0.25
22 | normalMapFilter: 0
23 | isReadable: 0
24 | streamingMipmaps: 0
25 | streamingMipmapsPriority: 0
26 | grayScaleToAlpha: 0
27 | generateCubemap: 6
28 | cubemapConvolution: 0
29 | seamlessCubemap: 0
30 | textureFormat: 1
31 | maxTextureSize: 2048
32 | textureSettings:
33 | serializedVersion: 2
34 | filterMode: 1
35 | aniso: 1
36 | mipBias: 0
37 | wrapU: 0
38 | wrapV: 0
39 | wrapW: 0
40 | nPOTScale: 1
41 | lightmap: 0
42 | compressionQuality: 50
43 | spriteMode: 0
44 | spriteExtrude: 1
45 | spriteMeshType: 1
46 | alignment: 0
47 | spritePivot: {x: 0.5, y: 0.5}
48 | spritePixelsToUnits: 100
49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
50 | spriteGenerateFallbackPhysicsShape: 1
51 | alphaUsage: 1
52 | alphaIsTransparency: 0
53 | spriteTessellationDetail: -1
54 | textureType: 0
55 | textureShape: 1
56 | singleChannelComponent: 0
57 | maxTextureSizeSet: 0
58 | compressionQualitySet: 0
59 | textureFormatSet: 0
60 | applyGammaDecoding: 0
61 | platformSettings:
62 | - serializedVersion: 3
63 | buildTarget: DefaultTexturePlatform
64 | maxTextureSize: 2048
65 | resizeAlgorithm: 0
66 | textureFormat: -1
67 | textureCompression: 1
68 | compressionQuality: 50
69 | crunchedCompression: 0
70 | allowsAlphaSplitting: 0
71 | overridden: 0
72 | androidETC2FallbackOverride: 0
73 | forceMaximumCompressionQuality_BC6H_BC7: 0
74 | spriteSheet:
75 | serializedVersion: 2
76 | sprites: []
77 | outline: []
78 | physicsShape: []
79 | bones: []
80 | spriteID:
81 | internalID: 0
82 | vertices: []
83 | indices:
84 | edges: []
85 | weights: []
86 | secondaryTextures: []
87 | spritePackingTag:
88 | pSDRemoveMatte: 0
89 | pSDShowRemoveMatteOption: 0
90 | userData:
91 | assetBundleName:
92 | assetBundleVariant:
93 |
--------------------------------------------------------------------------------
/Editor/tomduchene.unity-git-locks.editor.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Unity Git Locks Editor",
3 | "rootNamespace": "",
4 | "references": [],
5 | "includePlatforms": [
6 | "Editor"
7 | ],
8 | "excludePlatforms": [],
9 | "allowUnsafeCode": false,
10 | "overrideReferences": false,
11 | "precompiledReferences": [],
12 | "autoReferenced": true,
13 | "defineConstraints": [],
14 | "versionDefines": [],
15 | "noEngineReferences": false
16 | }
--------------------------------------------------------------------------------
/Editor/tomduchene.unity-git-locks.editor.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4d7332a5b7562a647b5f4cf48f15cd65
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Tom Duchene and Tactical-Adventures
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LICENSE.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fa42bc20fe5d65c4f8913277492c48e0
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Unity Git Locks
2 |
3 | Provides an extensive Unity integration for Git LFS locks, which are are essentials when working in teams to prevent conflicts, especially on binary files.
4 |
5 |
6 |
7 | ## Philosophy
8 |
9 | The plugin uses simple calls to the Git LFS locks commands, which means you have to make sure that your setup is configured correctly to use LFS locks through the CLI. It also makes it independent from any authentication process: if you can use it through command line it will work in Unity!
10 |
11 | This tool is intended to be used by developers, designers and artists alike, providing a flexible, easy and safe way of working as a team.
12 |
13 |
14 |
15 |
16 | ## How to use
17 |
18 | ### Installation
19 |
20 | The easiest way is probably to add this repository as a package, either:
21 |
22 | • [Automatically](https://docs.unity3d.com/Manual/upm-ui-giturl.html) (with url https://github.com/TomDuchene/unity-git-locks.git)
23 |
24 | • Manually, by cloning this repository in your repository's Packages folder
25 |
26 | ### Repository configuration recommendations
27 |
28 | We recommend that you setup your repository to **not** set as read-only the files that haven't been locked:
29 |
30 | `git config lfs.setlockablereadonly false`
31 |
32 | This allows users to modify any file and only prevents them from pushing changes to a locked file. The tool displays (opt-out) warnings when modifying a file that has been locked by someone else.
33 |
34 | It also means you won't have to declare which file types are lockable.
35 |
36 | You could in theory use the tool with the read-only mode enabled but it has not been tested.
37 |
38 | ### Initial setup
39 |
40 | The only thing you absolutely need to setup before use is your provider's (e.g. Github) username, so the tool knows that it's you.
41 | You have to input it in the Preferences window (`Edit/Preferences/Git Locks/Git host username`), which is also accessible through the "Git Locks" window.
42 |
43 |
44 |
45 | ### Troubleshooting
46 |
47 | Every git setup is a little bit different, so here's a [troubleshooting wiki page](https://github.com/TomDuchene/unity-git-locks/wiki/Troubleshooting) to help if you have errors.
48 |
49 |
50 |
51 |
52 | ## Features
53 |
54 | ### Main window
55 |
56 |
57 |
58 | Accessible through `Window/Git Locks`, this window will show you :
59 |
60 | - Refresh time information
61 | - A setup button to go back to the preferences
62 | - A button to manually trigger the locks refresh
63 | - An object field and button to lock an asset
64 | - A checkbox to select/unselect all your locks at once
65 | - A button to unlock all selected locks
66 | - A view of all the objects you have locked, with clickable object fields that focus the object in the project window, hoverable paths, and unlock buttons to release the lock on a file
67 | - A similar view of the locks owned by other team members
68 |
69 | ### Contextual menus
70 |
71 | 
72 |
73 | On a right-click, you can lock/unlock any file in the project window, and lock/unlock prefabs in the hierarchy window.
74 |
75 | Multi-selection and folder selection are supported, provided that the files in the selection are all lockable / all unlockable.
76 |
77 | In the project window, you can also show the file's git history, in a terminal (default) or in a browser (configurable through the settings).
78 |
79 | ### Lock icons
80 |
81 | Lock icons are displayed on all locked files and folders in the Git locks, Project, and Hierarchy windows. They can be clicked to obtain information on their status and unlock them directly (if they are yours).
82 |
83 | All locks are color-coded :
84 |
85 |
**File locked by you**
86 |
87 | You’re safe working on it (don’t forget to unlock it when you’re done). Also displayed on a folder containing at least one of your locks.
88 |
89 |
**File locked by someone else**
90 |
91 | You shouldn’t modify it (if you need to you can ask the owner of the lock to release it, if he/she’s drinking coconut water in Miami for the next two months you could probably force the unlock in the Git Locks window). Also displayed on a folder containing at least one of someone else's locks.
92 |
93 |
**File locked by someone else conflicting with your changes**
94 |
95 | Someone has locked this file but you’ve made changes to it anyway which you will not be able to push (if you’re just testing things out it’s ok, but you’ll have to discard changes before you push)
96 |
97 |
**Folder containing locks by you and someone else**
98 |
99 | This folder contains both locks of yours and others.
100 |
101 | ### Preferences
102 |
103 |
104 |
105 | Many options are available to customize your use of the tool:
106 |
107 | - **Max number of files grouped per request:** the bigger the number, the faster the overall locking/unlocking operation will be when processing dozens of files. If you go too high it might trigger the process timeout. Some users have errors where only one file can be processed at a time, it this case keep the value at 1
108 | - **Auto refresh toggle and time:** the refresh is done asynchronously, so you can set it pretty low if there are a lot of people working simultaneously. It is recommended to keep the auto refresh on.
109 | - **Notifications:** while not mandatory, most are really important for a smooth experience
110 | - **Other branches to check:** what branches to check in addition to the current one when verifying that the file you're about to lock hasn't been modified on the server. No spaces, comma separated.
111 | - **Number of my locks displayed:** used to show a minimum number of your own locks before the others'
112 | - **Colorblind mode:** changes all lock icons to have different shapes and a more inclusive color scheme
113 | - **Show debug logs:** show additional logs, especially the commands called
114 | - **Show Force buttons:** display additional Force Unlock buttons, which will probably work only if you have the rights to do so
115 | - **Show file history in browser:** Instead of showing the file history in a terminal, show it in your browser. You have to enter a URL corresponding to you host's format, for example "https://github.com/MyUserName/MyRepo/blob/$branch/$assetPath" (GitHub)
116 | - **Git version and update button:** you'll need to update if the version is too old
117 | - **Setup credentials manager button:** helps some users configure their CLI to authenticate if their credentials manager is not set correctly
118 |
119 | ### Keyboard shortcuts
120 |
121 | In the project window, you can use Ctrl + Maj + **L** or **U** to **L**ock/**U**nlock a file or selection of files.
122 |
123 | If you prefer having shortcuts to lock/unlock prefabs in the hierarchy, you can add some in `Edit / Shortcuts / “Main Menu/GameObject/Git LFS Lock” (or “Unlock”)`
124 |
125 |
126 |
127 |
128 | ## Misc
129 |
130 | This tool has been created by [Tom Duchêne](http://tomduchene.fr) at [Tactical Adventures](http://tactical-adventures.com), and is under the M.I.T. license.
131 | We might examine bug reports and pull requests but we can't offer you any kind of warranty or active support.
132 | We hope this tool helps your team as much as it helped ours ♥
133 |
134 |
--------------------------------------------------------------------------------
/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f6ab5a70c9ff2d844bd493390cb45cba
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.tomduchene.unity-git-locks",
3 | "displayName": "Git Locks",
4 | "version": "1.2.9",
5 | "description": "An editor tool to manipulate git LFS locks",
6 | "keywords": [
7 | "unity",
8 | "unity3d",
9 | "utility"
10 | ],
11 | "homepage": "https://github.com/TomDuchene/unity-git-locks",
12 | "bugs": {
13 | "url": "https://github.com/TomDuchene/unity-git-locks/issues"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+ssh://git@github.com/TomDuchene/unity-git-locks.git"
18 | },
19 | "license": "MIT",
20 | "author": "Tom Duchene (http://tomduchene.fr/)",
21 | "scripts": {
22 | "test": "echo \"Error: no test specified\" && exit 1"
23 | },
24 | "type": "Editor tool",
25 | "dependencies": {
26 | "com.unity.nuget.newtonsoft-json": "2.0.0"
27 | }
28 | }
--------------------------------------------------------------------------------
/package.json.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 10fc9bf638c381f45bd499786006a7f3
3 | PackageManifestImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------