🎨 Reformat everything to standard
Standard: - Use {} for blocks of GUI.Begin/End methods for explicit indentation purposes - Use explicit private - Explicitly state serializable/nonserialized intent on fields in serializable types - Reorder methods/members by - public enums - public delegates - const - static fields - instance fields - constructors - public static methods - public instance methods - public interface implementations - private static methods - private instance methods - private interface implementations - properties and indexers - nested types
This commit is contained in:
Родитель
849c9829ed
Коммит
cd317784d0
|
@ -0,0 +1,3 @@
|
|||
GitHub.Unity.dll*
|
||||
GitHub.Unity.pdb*
|
||||
GitHub.Unity.mdb*
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
|
@ -14,7 +12,7 @@ namespace GitHub.Unity
|
|||
}
|
||||
|
||||
// we do this so we're guaranteed to run on the main thread, not the loader thread
|
||||
static void Initialize()
|
||||
private static void Initialize()
|
||||
{
|
||||
EditorApplication.update -= Initialize;
|
||||
|
||||
|
@ -31,4 +29,4 @@ namespace GitHub.Unity
|
|||
Window.Initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<RunCodeAnalysis>false</RunCodeAnalysis>
|
||||
<CodeAnalysisRuleSet>..\common\GitHub.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
|
@ -47,6 +49,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="EntryPoint.cs" />
|
||||
<Compile Include="Guard.cs" />
|
||||
<Compile Include="Installer.cs" />
|
||||
<Compile Include="Localization.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
|
@ -71,6 +74,7 @@
|
|||
<Compile Include="Tasks\GitSwitchBranchesTask.cs" />
|
||||
<Compile Include="Tasks\GitTask.cs" />
|
||||
<Compile Include="Tasks\ProcessTask.cs" />
|
||||
<Compile Include="Tasks\TaskException.cs" />
|
||||
<Compile Include="Tasks\Tasks.cs" />
|
||||
<Compile Include="Tasks\TestTask.cs" />
|
||||
<Compile Include="Utility.cs" />
|
||||
|
@ -114,27 +118,9 @@
|
|||
<EmbeddedResource Include="Icons\tracked-branch-indicator.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Tasks\EvaluateProjectConfigurationTask.cs.meta" />
|
||||
<None Include="Tasks\FindGitTask.cs.meta" />
|
||||
<None Include="Tasks\GitAddTask.cs.meta" />
|
||||
<None Include="Tasks\GitBranchCreateTask.cs.meta" />
|
||||
<None Include="Tasks\GitCommitTask.cs.meta" />
|
||||
<None Include="Tasks\GitListBranchesTask.cs.meta" />
|
||||
<None Include="Tasks\GitListRemotesTask.cs.meta" />
|
||||
<None Include="Tasks\GitListUntrackedFilesTask.cs.meta" />
|
||||
<None Include="Tasks\GitLogTask.cs.meta" />
|
||||
<None Include="Tasks\GitStatusTask.cs.meta" />
|
||||
<None Include="Tasks\GitSwitchBranchesTask.cs.meta" />
|
||||
<None Include="Tasks\GitTask.cs.meta" />
|
||||
<None Include="Tasks\ProcessTask.cs.meta" />
|
||||
<None Include="Tasks\Tasks.cs.meta" />
|
||||
<None Include="Tasks\TestTask.cs.meta" />
|
||||
<None Include="Views\BranchesView.cs.meta" />
|
||||
<None Include="Views\ChangesetTreeView.cs.meta" />
|
||||
<None Include="Views\ChangesView.cs.meta" />
|
||||
<None Include="Views\HistoryView.cs.meta" />
|
||||
<None Include="Views\SettingsView.cs.meta" />
|
||||
<None Include="Views\View.cs.meta" />
|
||||
<None Include="..\common\GitHub.ruleset">
|
||||
<Link>GitHub.ruleset</Link>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace GitHub.Extensions
|
||||
{
|
||||
public static class Guard
|
||||
{
|
||||
public static void ArgumentNotNull(object value, string name)
|
||||
{
|
||||
if (value != null) return;
|
||||
string message = String.Format(CultureInfo.InvariantCulture, "Failed Null Check on '{0}'", name);
|
||||
#if DEBUG
|
||||
if (!InUnitTestRunner)
|
||||
{
|
||||
Debug.Fail(message);
|
||||
}
|
||||
#endif
|
||||
throw new ArgumentNullException(name, message);
|
||||
}
|
||||
|
||||
public static void ArgumentNonNegative(int value, string name)
|
||||
{
|
||||
if (value > -1) return;
|
||||
|
||||
var message = String.Format(CultureInfo.InvariantCulture, "The value for '{0}' must be non-negative", name);
|
||||
#if DEBUG
|
||||
if (!InUnitTestRunner)
|
||||
{
|
||||
Debug.Fail(message);
|
||||
}
|
||||
#endif
|
||||
throw new ArgumentException(message, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks a string argument to ensure it isn't null or empty.
|
||||
/// </summary>
|
||||
/// <param name = "value">The argument value to check.</param>
|
||||
/// <param name = "name">The name of the argument.</param>
|
||||
public static void ArgumentNotEmptyString(string value, string name)
|
||||
{
|
||||
if (value?.Length > 0) return;
|
||||
string message = String.Format(CultureInfo.InvariantCulture, "The value for '{0}' must not be empty", name);
|
||||
#if DEBUG
|
||||
if (!InUnitTestRunner)
|
||||
{
|
||||
Debug.Fail(message);
|
||||
}
|
||||
#endif
|
||||
throw new ArgumentException(message, name);
|
||||
}
|
||||
|
||||
public static void ArgumentInRange(int value, int minValue, string name)
|
||||
{
|
||||
if (value >= minValue) return;
|
||||
string message = String.Format(CultureInfo.InvariantCulture,
|
||||
"The value '{0}' for '{1}' must be greater than or equal to '{2}'",
|
||||
value,
|
||||
name,
|
||||
minValue);
|
||||
#if DEBUG
|
||||
if (!InUnitTestRunner)
|
||||
{
|
||||
Debug.Fail(message);
|
||||
}
|
||||
#endif
|
||||
throw new ArgumentOutOfRangeException(name, message);
|
||||
}
|
||||
|
||||
public static void ArgumentInRange(int value, int minValue, int maxValue, string name)
|
||||
{
|
||||
if (value >= minValue && value <= maxValue) return;
|
||||
string message = String.Format(CultureInfo.InvariantCulture,
|
||||
"The value '{0}' for '{1}' must be greater than or equal to '{2}' and less than or equal to '{3}'",
|
||||
value,
|
||||
name,
|
||||
minValue,
|
||||
maxValue);
|
||||
#if DEBUG
|
||||
if (!InUnitTestRunner)
|
||||
{
|
||||
Debug.Fail(message);
|
||||
}
|
||||
#endif
|
||||
throw new ArgumentOutOfRangeException(name, message);
|
||||
}
|
||||
|
||||
// Borrowed from Splat.
|
||||
|
||||
public static bool InUnitTestRunner { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,21 +1,20 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
class Installer : ScriptableObject
|
||||
{
|
||||
const string PackageName = "GitHub extensions";
|
||||
const string
|
||||
QueryTitle = "Embed " + PackageName + "?",
|
||||
QueryMessage = "This package has no project dependencies and so can either run embedded in your Unity install or remain in your assets folder.\n\nWould you like to embed it?",
|
||||
QueryOK = "Embed",
|
||||
QueryCancel = "Cancel",
|
||||
ErrorTitle = "Installer error",
|
||||
ErrorMessage = "An error occured during installation:\n{0}",
|
||||
ErrorOK = "OK";
|
||||
private const string PackageName = "GitHub extensions";
|
||||
private const string QueryTitle = "Embed " + PackageName + "?";
|
||||
private const string QueryMessage =
|
||||
"This package has no project dependencies and so can either run embedded in your Unity install or remain in your assets folder.\n\nWould you like to embed it?";
|
||||
private const string QueryOK = "Embed";
|
||||
private const string QueryCancel = "Cancel";
|
||||
private const string ErrorTitle = "Installer error";
|
||||
private const string ErrorMessage = "An error occured during installation:\n{0}";
|
||||
private const string ErrorOK = "OK";
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
|
@ -26,12 +25,12 @@ namespace GitHub.Unity
|
|||
|
||||
// Detect install path
|
||||
string selfPath;
|
||||
Installer instance = FindObjectOfType(typeof(Installer)) as Installer;
|
||||
var instance = FindObjectOfType(typeof(Installer)) as Installer;
|
||||
if (instance == null)
|
||||
{
|
||||
instance = CreateInstance<Installer>();
|
||||
}
|
||||
MonoScript script = MonoScript.FromScriptableObject(instance);
|
||||
var script = MonoScript.FromScriptableObject(instance);
|
||||
if (script == null)
|
||||
{
|
||||
selfPath = string.Empty;
|
||||
|
@ -42,14 +41,14 @@ namespace GitHub.Unity
|
|||
}
|
||||
DestroyImmediate(instance);
|
||||
|
||||
if (string.IsNullOrEmpty(selfPath))
|
||||
// If we cannot self-locate then forget the whole thing
|
||||
if (string.IsNullOrEmpty(selfPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (EditorUtility.DisplayDialog(QueryTitle, QueryMessage, QueryOK, QueryCancel))
|
||||
// Perform move
|
||||
if (EditorUtility.DisplayDialog(QueryTitle, QueryMessage, QueryOK, QueryCancel))
|
||||
{
|
||||
MoveFrom(Application.dataPath + selfPath.Substring("Assets".Length, selfPath.LastIndexOf('/') - "Assets".Length));
|
||||
}
|
||||
|
@ -58,8 +57,7 @@ namespace GitHub.Unity
|
|||
AssetDatabase.DeleteAsset(selfPath);
|
||||
}
|
||||
|
||||
|
||||
static void MoveFrom(string path)
|
||||
private static void MoveFrom(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -72,11 +70,9 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
static void Failure(string error)
|
||||
private static void Failure(string error)
|
||||
{
|
||||
EditorUtility.DisplayDialog(ErrorTitle, string.Format(ErrorMessage, error), ErrorOK);
|
||||
EditorUtility.DisplayDialog(ErrorTitle, String.Format(ErrorMessage, error), ErrorOK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
class ProjectWindowInterface : AssetPostprocessor
|
||||
class ProjectWindowInterface : AssetPostprocessor
|
||||
{
|
||||
static List<GitStatusEntry> entries = new List<GitStatusEntry>();
|
||||
static List<string> guids = new List<string>();
|
||||
private static readonly List<GitStatusEntry> entries = new List<GitStatusEntry>();
|
||||
private static readonly List<string> guids = new List<string>();
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
|
@ -19,74 +18,61 @@ namespace GitHub.Unity
|
|||
Tasks.ScheduleMainThread(() => Refresh());
|
||||
}
|
||||
|
||||
|
||||
static void OnPostprocessAllAssets(string[] imported, string[] deleted, string[] moveDestination, string[] moveSource)
|
||||
private static void OnPostprocessAllAssets(string[] imported, string[] deleted, string[] moveDestination, string[] moveSource)
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
|
||||
static void Refresh()
|
||||
private static void Refresh()
|
||||
{
|
||||
GitStatusTask.Schedule();
|
||||
}
|
||||
|
||||
|
||||
static void OnStatusUpdate(GitStatus update)
|
||||
private static void OnStatusUpdate(GitStatus update)
|
||||
{
|
||||
entries.Clear();
|
||||
entries.AddRange(update.Entries);
|
||||
|
||||
guids.Clear();
|
||||
for (int index = 0; index < entries.Count; ++index)
|
||||
for (var index = 0; index < entries.Count; ++index)
|
||||
{
|
||||
string path = entries[index].ProjectPath;
|
||||
var path = entries[index].ProjectPath;
|
||||
guids.Add(string.IsNullOrEmpty(path) ? string.Empty : AssetDatabase.AssetPathToGUID(path));
|
||||
}
|
||||
|
||||
EditorApplication.RepaintProjectWindow();
|
||||
}
|
||||
|
||||
|
||||
static void OnProjectWindowItemGUI(string guid, Rect itemRect)
|
||||
private static void OnProjectWindowItemGUI(string guid, Rect itemRect)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint || string.IsNullOrEmpty(guid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int index = guids.IndexOf(guid);
|
||||
var index = guids.IndexOf(guid);
|
||||
if (index < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Texture2D texture = Styles.GetGitFileStatusIcon(entries[index].Status);
|
||||
var texture = Styles.GetGitFileStatusIcon(entries[index].Status);
|
||||
Rect rect;
|
||||
|
||||
if (itemRect.width > itemRect.height)
|
||||
// End of row placement
|
||||
if (itemRect.width > itemRect.height)
|
||||
{
|
||||
rect = new Rect(itemRect.xMax - texture.width, itemRect.y, texture.width, Mathf.Min(texture.height, EditorGUIUtility.singleLineHeight));
|
||||
rect = new Rect(itemRect.xMax - texture.width, itemRect.y, texture.width,
|
||||
Mathf.Min(texture.height, EditorGUIUtility.singleLineHeight));
|
||||
}
|
||||
else
|
||||
// Corner placement
|
||||
// TODO: Magic numbers that need reviewing. Make sure this works properly with long filenames and wordwrap.
|
||||
else
|
||||
{
|
||||
float scale = itemRect.height / 90f;
|
||||
Vector2
|
||||
size = new Vector2(texture.width * scale, texture.height * scale),
|
||||
offset = new Vector2(
|
||||
itemRect.width * Mathf.Min(.4f * scale, .2f),
|
||||
itemRect.height * Mathf.Min(.2f * scale, .2f)
|
||||
);
|
||||
|
||||
rect = new Rect(
|
||||
itemRect.center.x - size.x * .5f + offset.x,
|
||||
itemRect.center.y - size.y * .5f + offset.y,
|
||||
size.x,
|
||||
size.y
|
||||
);
|
||||
var scale = itemRect.height / 90f;
|
||||
var size = new Vector2(texture.width * scale, texture.height * scale);
|
||||
var offset = new Vector2(itemRect.width * Mathf.Min(.4f * scale, .2f), itemRect.height * Mathf.Min(.2f * scale, .2f));
|
||||
rect = new Rect(itemRect.center.x - size.x * .5f + offset.x, itemRect.center.y - size.y * .5f + offset.y, size.x, size.y);
|
||||
}
|
||||
|
||||
GUI.DrawTexture(rect, texture, ScaleMode.ScaleToFit);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -8,12 +9,13 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyTitle("GitHub.Unity")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyCompany("GitHub")]
|
||||
[assembly: AssemblyProduct("GitHub.Unity")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2017")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
|
@ -34,3 +36,5 @@ using System.Runtime.InteropServices;
|
|||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
[assembly: NeutralResourcesLanguage("en-US")]
|
||||
|
||||
|
|
|
@ -1,81 +1,38 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
public class Settings : ScriptableObject
|
||||
{
|
||||
const string
|
||||
SettingsParseError = "Failed to parse settings file at '{0}'",
|
||||
RelativeSettingsPath = "{0}/ProjectSettings/{1}",
|
||||
LocalSettingsName = "GitHub.local.json",
|
||||
TeamSettingsName = "GitHub.json";
|
||||
private const string SettingsParseError = "Failed to parse settings file at '{0}'";
|
||||
private const string RelativeSettingsPath = "{0}/ProjectSettings/{1}";
|
||||
private const string LocalSettingsName = "GitHub.local.json";
|
||||
private const string TeamSettingsName = "GitHub.json";
|
||||
|
||||
private static Settings asset;
|
||||
|
||||
[SerializeField] List<string>
|
||||
keys = new List<string>(),
|
||||
teamKeys = new List<string>();
|
||||
[SerializeField] List<object>
|
||||
values = new List<object>(),
|
||||
teamValues = new List<object>();
|
||||
|
||||
|
||||
static Settings asset;
|
||||
|
||||
|
||||
static string GetLocalPath()
|
||||
{
|
||||
return string.Format(RelativeSettingsPath, Utility.UnityProjectPath, LocalSettingsName);
|
||||
}
|
||||
|
||||
|
||||
static string GetTeamPath()
|
||||
{
|
||||
return string.Format(RelativeSettingsPath, Utility.UnityProjectPath, TeamSettingsName);
|
||||
}
|
||||
|
||||
|
||||
static IDictionary<string, object> LoadSettings(string path)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
object parseResult;
|
||||
IDictionary<string, object> settings;
|
||||
|
||||
if(!SimpleJson.TryDeserializeObject(File.ReadAllText(path), out parseResult) || (settings = parseResult as IDictionary<string, object>) == null)
|
||||
{
|
||||
Debug.LogErrorFormat(SettingsParseError, path);
|
||||
return null;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
[SerializeField] private List<string> keys = new List<string>();
|
||||
[SerializeField] private List<string> teamKeys = new List<string>();
|
||||
[SerializeField] private List<object> teamValues = new List<object>();
|
||||
[SerializeField] private List<object> values = new List<object>();
|
||||
|
||||
public static bool Reload()
|
||||
{
|
||||
Settings newAsset = CreateInstance<Settings>();
|
||||
var newAsset = CreateInstance<Settings>();
|
||||
|
||||
IDictionary<string, object> settings = LoadSettings(GetLocalPath());
|
||||
var settings = LoadSettings(GetLocalPath());
|
||||
|
||||
if (settings != null)
|
||||
{
|
||||
newAsset.keys.AddRange(settings.Keys);
|
||||
newAsset.values.AddRange(settings.Values.Select(
|
||||
v => (v is IList<object>) ?
|
||||
(object)new List<string>(((IList<object>)v).Select(v2 => v2 as string))
|
||||
:
|
||||
(object)(v as string)
|
||||
));
|
||||
newAsset.values.AddRange(settings.Values.Select(v => v is IList<object>
|
||||
? (object)new List<string>(((IList<object>)v).Select(v2 => v2 as string))
|
||||
: (object)(v as string)));
|
||||
}
|
||||
|
||||
settings = LoadSettings(GetTeamPath());
|
||||
|
@ -83,12 +40,9 @@ namespace GitHub.Unity
|
|||
if (settings != null)
|
||||
{
|
||||
newAsset.teamKeys.AddRange(settings.Keys);
|
||||
newAsset.teamValues.AddRange(settings.Values.Select(
|
||||
v => (v is IList<object>) ?
|
||||
(object)new List<string>(((IList<object>)v).Select(v2 => v2 as string))
|
||||
:
|
||||
(object)(v as string)
|
||||
));
|
||||
newAsset.teamValues.AddRange(settings.Values.Select(v => v is IList<object>
|
||||
? (object)new List<string>(((IList<object>)v).Select(v2 => v2 as string))
|
||||
: (object)(v as string)));
|
||||
}
|
||||
|
||||
asset = newAsset;
|
||||
|
@ -96,7 +50,6 @@ namespace GitHub.Unity
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static bool Save()
|
||||
{
|
||||
if (asset == null)
|
||||
|
@ -104,74 +57,47 @@ namespace GitHub.Unity
|
|||
return false;
|
||||
}
|
||||
|
||||
string path = GetLocalPath();
|
||||
var path = GetLocalPath();
|
||||
|
||||
StreamWriter settings = File.CreateText(path);
|
||||
settings.Write("{\n");
|
||||
var settings = File.CreateText(path);
|
||||
settings.WriteLine("{");
|
||||
|
||||
for (int index = 0; index < asset.keys.Count; ++index)
|
||||
for (var index = 0; index < asset.keys.Count; ++index)
|
||||
{
|
||||
List<string> list = asset.values[index] as List<string>;
|
||||
var list = asset.values[index] as List<string>;
|
||||
|
||||
if (list == null)
|
||||
{
|
||||
settings.Write("\t\"{0}\": \"{1}\",\n", Escape(asset.keys[index]), Escape((string)asset.values[index]));
|
||||
settings.WriteLine("\t\"{0}\": \"{1}\",", Escape(asset.keys[index]), Escape((string)asset.values[index]));
|
||||
}
|
||||
else
|
||||
{
|
||||
settings.Write("\t\"{0}\":\n\t[\n", Escape(asset.keys[index]));
|
||||
for (int listIndex = 0; listIndex < list.Count; ++listIndex)
|
||||
settings.WriteLine("\t\"{0}\":\n\t[", Escape(asset.keys[index]));
|
||||
for (var listIndex = 0; listIndex < list.Count; ++listIndex)
|
||||
{
|
||||
settings.Write("\t\t\"{0}\",\n", Escape(list[listIndex]));
|
||||
settings.WriteLine("\t\t\"{0}\",", Escape(list[listIndex]));
|
||||
}
|
||||
settings.Write("\t],\n");
|
||||
|
||||
settings.WriteLine("\t],");
|
||||
}
|
||||
}
|
||||
|
||||
settings.Write("}\n");
|
||||
settings.WriteLine("}");
|
||||
settings.Close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static string Escape(string unescaped)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(unescaped);
|
||||
|
||||
builder.Replace("\\", "\\\\");
|
||||
builder.Replace("\"", "\\\"");
|
||||
builder.Replace("\n", "\\n");
|
||||
builder.Replace("\r", "\\r");
|
||||
builder.Replace("\t", "\\t");
|
||||
builder.Replace("\b", "\\b");
|
||||
builder.Replace("\f", "\\f");
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
|
||||
static Settings GetAsset()
|
||||
{
|
||||
if (asset == null)
|
||||
{
|
||||
Reload();
|
||||
}
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
|
||||
public static string Get(string key, string fallback = "")
|
||||
{
|
||||
Settings asset = GetAsset();
|
||||
var asset = GetAsset();
|
||||
|
||||
if (asset == null)
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
|
||||
int index = asset.teamKeys.IndexOf(key);
|
||||
var index = asset.teamKeys.IndexOf(key);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
|
@ -188,10 +114,9 @@ namespace GitHub.Unity
|
|||
return fallback;
|
||||
}
|
||||
|
||||
|
||||
public static bool Set(string key, string value, bool noSave = false)
|
||||
{
|
||||
Settings asset = GetAsset();
|
||||
var asset = GetAsset();
|
||||
|
||||
if (asset == null)
|
||||
{
|
||||
|
@ -203,7 +128,7 @@ namespace GitHub.Unity
|
|||
return false;
|
||||
}
|
||||
|
||||
int index = asset.keys.IndexOf(key);
|
||||
var index = asset.keys.IndexOf(key);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
|
@ -228,10 +153,9 @@ namespace GitHub.Unity
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static bool Unset(string key, bool noSave = false)
|
||||
{
|
||||
Settings asset = GetAsset();
|
||||
var asset = GetAsset();
|
||||
|
||||
if (asset == null)
|
||||
{
|
||||
|
@ -243,7 +167,7 @@ namespace GitHub.Unity
|
|||
return false;
|
||||
}
|
||||
|
||||
int index = asset.keys.IndexOf(key);
|
||||
var index = asset.keys.IndexOf(key);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
|
@ -261,10 +185,9 @@ namespace GitHub.Unity
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static bool Rename(string key, string newKey, bool noSave = false)
|
||||
{
|
||||
Settings asset = GetAsset();
|
||||
var asset = GetAsset();
|
||||
|
||||
if (asset == null)
|
||||
{
|
||||
|
@ -276,7 +199,7 @@ namespace GitHub.Unity
|
|||
return false;
|
||||
}
|
||||
|
||||
int index = asset.keys.IndexOf(key);
|
||||
var index = asset.keys.IndexOf(key);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
|
@ -293,156 +216,16 @@ namespace GitHub.Unity
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
static List<string> GetLocalList(string key)
|
||||
{
|
||||
Settings asset = GetAsset();
|
||||
|
||||
if (asset == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int index = asset.keys.IndexOf(key);
|
||||
return index < 0 ? null : asset.values[index] as List<string>;
|
||||
}
|
||||
|
||||
|
||||
static List<string> GetTeamList(string key)
|
||||
{
|
||||
Settings asset = GetAsset();
|
||||
|
||||
if (asset == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int index = asset.teamKeys.IndexOf(key);
|
||||
return index < 0 ? null : asset.teamValues[index] as List<string>;
|
||||
}
|
||||
|
||||
|
||||
public static int CountElements(string key)
|
||||
{
|
||||
List<string>
|
||||
localList = GetLocalList(key),
|
||||
teamList = GetTeamList(key);
|
||||
|
||||
return (localList == null ? 0 : teamList.Count) + (teamList == null ? 0 : teamList.Count);
|
||||
}
|
||||
|
||||
|
||||
public static int GetElementIndex(string key, string value)
|
||||
{
|
||||
List<string>
|
||||
localList = GetLocalList(key),
|
||||
teamList = GetTeamList(key);
|
||||
|
||||
int index = (teamList == null ? -1 : teamList.IndexOf(value));
|
||||
if (index > -1)
|
||||
{
|
||||
return index + (localList == null ? 0 : localList.Count);
|
||||
}
|
||||
|
||||
return localList == null ? -1 : localList.IndexOf(value);
|
||||
}
|
||||
|
||||
|
||||
public static string GetElement(string key, int index, string fallback = "")
|
||||
{
|
||||
List<string>
|
||||
localList = GetLocalList(key),
|
||||
teamList = GetTeamList(key);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
|
||||
if (localList != null && index < localList.Count)
|
||||
{
|
||||
return localList[index];
|
||||
}
|
||||
|
||||
if (teamList != null && index < teamList.Count + (localList == null ? 0 : localList.Count))
|
||||
{
|
||||
return teamList[index];
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
|
||||
public static bool SetElement(string key, int index, string value, bool noSave = false)
|
||||
{
|
||||
List<string> localList = GetLocalList(key);
|
||||
|
||||
if (localList == null || index >= localList.Count || index < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
localList[index] = value;
|
||||
|
||||
if (!noSave)
|
||||
{
|
||||
Save();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static bool RemoveElement(string key, string value, bool noSave = false)
|
||||
{
|
||||
List<string> localList = GetLocalList(key);
|
||||
|
||||
if (localList == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
localList.Remove(value);
|
||||
|
||||
if (!noSave)
|
||||
{
|
||||
Save();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static bool RemoveElementAt(string key, int index, bool noSave = false)
|
||||
{
|
||||
List<string> localList = GetLocalList(key);
|
||||
|
||||
if (localList == null || index >= localList.Count || index < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
localList.RemoveAt(index);
|
||||
|
||||
if (!noSave)
|
||||
{
|
||||
Save();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static bool AddElement(string key, string value, bool noSave = false)
|
||||
{
|
||||
Settings asset = GetAsset();
|
||||
var asset = GetAsset();
|
||||
|
||||
if (asset == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int index = asset.keys.IndexOf(key);
|
||||
var index = asset.keys.IndexOf(key);
|
||||
|
||||
List<string> list = null;
|
||||
|
||||
|
@ -466,5 +249,188 @@ namespace GitHub.Unity
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool RemoveElement(string key, string value, bool noSave = false)
|
||||
{
|
||||
var localList = GetLocalList(key);
|
||||
|
||||
if (localList == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
localList.Remove(value);
|
||||
|
||||
if (!noSave)
|
||||
{
|
||||
Save();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool RemoveElementAt(string key, int index, bool noSave = false)
|
||||
{
|
||||
var localList = GetLocalList(key);
|
||||
|
||||
if (localList == null || index >= localList.Count || index < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
localList.RemoveAt(index);
|
||||
|
||||
if (!noSave)
|
||||
{
|
||||
Save();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string GetElement(string key, int index, string fallback = "")
|
||||
{
|
||||
if (index < 0)
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
|
||||
var localList = GetLocalList(key);
|
||||
var teamList = GetTeamList(key);
|
||||
|
||||
if (localList != null && index < localList.Count)
|
||||
{
|
||||
return localList[index];
|
||||
}
|
||||
|
||||
if (teamList != null && index < teamList.Count + (localList == null ? 0 : localList.Count))
|
||||
{
|
||||
return teamList[index];
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public static int GetElementIndex(string key, string value)
|
||||
{
|
||||
var localList = GetLocalList(key);
|
||||
var teamList = GetTeamList(key);
|
||||
|
||||
var index = teamList == null ? -1 : teamList.IndexOf(value);
|
||||
if (index > -1)
|
||||
{
|
||||
return index + (localList == null ? 0 : localList.Count);
|
||||
}
|
||||
|
||||
return localList == null ? -1 : localList.IndexOf(value);
|
||||
}
|
||||
|
||||
public static bool SetElement(string key, int index, string value, bool noSave = false)
|
||||
{
|
||||
var localList = GetLocalList(key);
|
||||
|
||||
if (localList == null || index >= localList.Count || index < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
localList[index] = value;
|
||||
|
||||
if (!noSave)
|
||||
{
|
||||
Save();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static int CountElements(string key)
|
||||
{
|
||||
var localList = GetLocalList(key);
|
||||
var teamList = GetTeamList(key);
|
||||
|
||||
return (localList == null ? 0 : teamList.Count) + (teamList == null ? 0 : teamList.Count);
|
||||
}
|
||||
|
||||
private static string GetLocalPath()
|
||||
{
|
||||
return String.Format(RelativeSettingsPath, Utility.UnityProjectPath, LocalSettingsName);
|
||||
}
|
||||
|
||||
private static string GetTeamPath()
|
||||
{
|
||||
return String.Format(RelativeSettingsPath, Utility.UnityProjectPath, TeamSettingsName);
|
||||
}
|
||||
|
||||
private static IDictionary<string, object> LoadSettings(string path)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
object parseResult;
|
||||
IDictionary<string, object> settings;
|
||||
|
||||
if (!SimpleJson.TryDeserializeObject(File.ReadAllText(path), out parseResult) ||
|
||||
(settings = parseResult as IDictionary<string, object>) == null)
|
||||
{
|
||||
Debug.LogErrorFormat(SettingsParseError, path);
|
||||
return null;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static string Escape(string unescaped)
|
||||
{
|
||||
var builder = new StringBuilder(unescaped);
|
||||
|
||||
builder.Replace("\\", "\\\\");
|
||||
builder.Replace("\"", "\\\"");
|
||||
builder.Replace("\n", "\\n");
|
||||
builder.Replace("\r", "\\r");
|
||||
builder.Replace("\t", "\\t");
|
||||
builder.Replace("\b", "\\b");
|
||||
builder.Replace("\f", "\\f");
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static Settings GetAsset()
|
||||
{
|
||||
if (asset == null)
|
||||
{
|
||||
Reload();
|
||||
}
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
private static List<string> GetLocalList(string key)
|
||||
{
|
||||
var asset = GetAsset();
|
||||
|
||||
if (asset == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var index = asset.keys.IndexOf(key);
|
||||
return index < 0 ? null : asset.values[index] as List<string>;
|
||||
}
|
||||
|
||||
private static List<string> GetTeamList(string key)
|
||||
{
|
||||
var asset = GetAsset();
|
||||
|
||||
if (asset == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var index = asset.teamKeys.IndexOf(key);
|
||||
return index < 0 ? null : asset.teamValues[index] as List<string>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -420,7 +420,7 @@ namespace GitHub.Unity
|
|||
public static void Warning(string message)
|
||||
{
|
||||
GUILayout.BeginHorizontal(EditorStyles.helpBox);
|
||||
GUILayout.Label(string.Format(WarningLabel, message), Styles.LongMessageStyle);
|
||||
GUILayout.Label(String.Format(WarningLabel, message), Styles.LongMessageStyle);
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
|
|
@ -1,278 +1,208 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using System.Threading;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
[Flags]
|
||||
enum ProjectSettingsEvaluation
|
||||
{
|
||||
None = 0,
|
||||
EditorSettingsMissing = 1 << 0,
|
||||
BadVCSSettings = 1 << 1,
|
||||
BinarySerialization = 1 << 2,
|
||||
MixedSerialization = 1 << 3
|
||||
None = 0,
|
||||
EditorSettingsMissing = 1 << 0,
|
||||
BadVCSSettings = 1 << 1,
|
||||
BinarySerialization = 1 << 2,
|
||||
MixedSerialization = 1 << 3
|
||||
}
|
||||
|
||||
|
||||
enum GitIgnoreRuleEffect
|
||||
{
|
||||
Require = 0,
|
||||
Disallow = 1
|
||||
}
|
||||
|
||||
|
||||
abstract class ProjectConfigurationIssue
|
||||
{}
|
||||
|
||||
|
||||
class ProjectSettingsIssue : ProjectConfigurationIssue
|
||||
{
|
||||
public ProjectSettingsEvaluation Evaluation { get; protected set; }
|
||||
|
||||
|
||||
public ProjectSettingsIssue(ProjectSettingsEvaluation evaluation)
|
||||
{
|
||||
Evaluation = evaluation;
|
||||
}
|
||||
|
||||
|
||||
public bool WasCaught(ProjectSettingsEvaluation evaluation)
|
||||
{
|
||||
return (Evaluation & evaluation) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public ProjectSettingsEvaluation Evaluation { get; protected set; }
|
||||
}
|
||||
|
||||
class GitIgnoreException : ProjectConfigurationIssue
|
||||
{
|
||||
public Exception Exception { get; protected set; }
|
||||
|
||||
|
||||
public GitIgnoreException(Exception exception)
|
||||
{
|
||||
Exception = exception;
|
||||
}
|
||||
}
|
||||
|
||||
public Exception Exception { get; protected set; }
|
||||
}
|
||||
|
||||
class GitIgnoreIssue : ProjectConfigurationIssue
|
||||
{
|
||||
public string File { get; protected set; }
|
||||
public string Line { get; protected set; }
|
||||
public string Description { get; protected set; }
|
||||
|
||||
|
||||
public GitIgnoreIssue(string file, string line, string description)
|
||||
{
|
||||
File = file;
|
||||
Line = line;
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
|
||||
public string File { get; protected set; }
|
||||
public string Line { get; protected set; }
|
||||
public string Description { get; protected set; }
|
||||
}
|
||||
|
||||
struct GitIgnoreRule
|
||||
{
|
||||
const string
|
||||
CountKey = "GitIgnoreRuleCount",
|
||||
EffectKey = "GitIgnoreRule{0}Effect",
|
||||
FileKey = "GitIgnoreRule{0}File",
|
||||
LineKey = "GitIgnoreRule{0}Line",
|
||||
TriggetTextKey = "GitIgnoreRule{0}TriggerText";
|
||||
|
||||
|
||||
public static int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return Mathf.Max(0, int.Parse(Settings.Get(CountKey, "0")));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public GitIgnoreRuleEffect Effect { get; private set; }
|
||||
public string FileString { get; private set; }
|
||||
public string LineString { get; private set; }
|
||||
public Regex File { get; private set; }
|
||||
public Regex Line { get; private set; }
|
||||
public string TriggerText { get; private set; }
|
||||
|
||||
private const string CountKey = "GitIgnoreRuleCount";
|
||||
private const string EffectKey = "GitIgnoreRule{0}Effect";
|
||||
private const string FileKey = "GitIgnoreRule{0}File";
|
||||
private const string LineKey = "GitIgnoreRule{0}Line";
|
||||
private const string TriggerTextKey = "GitIgnoreRule{0}TriggerText";
|
||||
|
||||
public static bool TryLoad(int index, out GitIgnoreRule result)
|
||||
{
|
||||
result = new GitIgnoreRule();
|
||||
|
||||
int effect;
|
||||
if (!int.TryParse(Settings.Get(string.Format(EffectKey, index), "-1"), out effect) || effect < 0)
|
||||
if (!int.TryParse(Settings.Get(String.Format(EffectKey, index), "-1"), out effect) || effect < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
result.Effect = (GitIgnoreRuleEffect)effect;
|
||||
|
||||
result.FileString = Settings.Get(string.Format(FileKey, index));
|
||||
result.FileString = Settings.Get(String.Format(FileKey, index));
|
||||
|
||||
try
|
||||
{
|
||||
result.File = new Regex(result.FileString);
|
||||
}
|
||||
catch(ArgumentException e)
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
result.File = null;
|
||||
}
|
||||
|
||||
result.LineString = Settings.Get(string.Format(LineKey, index));
|
||||
result.LineString = Settings.Get(String.Format(LineKey, index));
|
||||
|
||||
try
|
||||
{
|
||||
result.Line = new Regex(result.LineString);
|
||||
}
|
||||
catch(ArgumentException e)
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
result.Line = null;
|
||||
}
|
||||
|
||||
result.TriggerText = Settings.Get(string.Format(TriggetTextKey, index));
|
||||
result.TriggerText = Settings.Get(String.Format(TriggerTextKey, index));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static void Save(int index, GitIgnoreRuleEffect effect, string file, string line, string triggerText)
|
||||
{
|
||||
Settings.Set(string.Format(EffectKey, index), ((int)effect).ToString(), true);
|
||||
Settings.Set(string.Format(FileKey, index), file, true);
|
||||
Settings.Set(string.Format(LineKey, index), line, true);
|
||||
Settings.Set(string.Format(TriggetTextKey, index), triggerText);
|
||||
Settings.Set(String.Format(EffectKey, index), ((int)effect).ToString(), true);
|
||||
Settings.Set(String.Format(FileKey, index), file, true);
|
||||
Settings.Set(String.Format(LineKey, index), line, true);
|
||||
Settings.Set(String.Format(TriggerTextKey, index), triggerText);
|
||||
}
|
||||
|
||||
|
||||
public static void New()
|
||||
{
|
||||
Save(Count, GitIgnoreRuleEffect.Require, "", "", "");
|
||||
Settings.Set(CountKey, (Count + 1).ToString());
|
||||
}
|
||||
|
||||
|
||||
public static void Delete(int index)
|
||||
{
|
||||
Settings.Unset(string.Format(EffectKey, index), true);
|
||||
Settings.Unset(string.Format(FileKey, index), true);
|
||||
Settings.Unset(string.Format(LineKey, index), true);
|
||||
Settings.Unset(string.Format(TriggetTextKey, index), true);
|
||||
Settings.Unset(String.Format(EffectKey, index), true);
|
||||
Settings.Unset(String.Format(FileKey, index), true);
|
||||
Settings.Unset(String.Format(LineKey, index), true);
|
||||
Settings.Unset(String.Format(TriggerTextKey, index), true);
|
||||
|
||||
int count = Count;
|
||||
for (int current = index + 1; index < count; ++index)
|
||||
var count = Count;
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
Settings.Rename(string.Format(EffectKey, index), string.Format(EffectKey, index - 1), true);
|
||||
Settings.Rename(string.Format(FileKey, index), string.Format(FileKey, index - 1), true);
|
||||
Settings.Rename(string.Format(LineKey, index), string.Format(LineKey, index - 1), true);
|
||||
Settings.Rename(string.Format(TriggetTextKey, index), string.Format(TriggetTextKey, index - 1), true);
|
||||
Settings.Rename(String.Format(EffectKey, index), String.Format(EffectKey, index - 1), true);
|
||||
Settings.Rename(String.Format(FileKey, index), String.Format(FileKey, index - 1), true);
|
||||
Settings.Rename(String.Format(LineKey, index), String.Format(LineKey, index - 1), true);
|
||||
Settings.Rename(String.Format(TriggerTextKey, index), String.Format(TriggerTextKey, index - 1), true);
|
||||
}
|
||||
|
||||
Settings.Set(CountKey, (count - 1).ToString());
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0} \"{1}\" in \"{2}\": {3}", Effect, Line, File, TriggerText);
|
||||
return String.Format("{0} \"{1}\" in \"{2}\": {3}", Effect, Line, File, TriggerText);
|
||||
}
|
||||
}
|
||||
|
||||
public static int Count
|
||||
{
|
||||
get { return Mathf.Max(0, int.Parse(Settings.Get(CountKey, "0"))); }
|
||||
}
|
||||
|
||||
public GitIgnoreRuleEffect Effect { get; private set; }
|
||||
public string FileString { get; private set; }
|
||||
public string LineString { get; private set; }
|
||||
public Regex File { get; private set; }
|
||||
public Regex Line { get; private set; }
|
||||
public string TriggerText { get; private set; }
|
||||
}
|
||||
|
||||
class EvaluateProjectConfigurationTask : ITask
|
||||
{
|
||||
enum SerializationSetting
|
||||
{
|
||||
Mixed = 0,
|
||||
ForceBinary = 1,
|
||||
ForceText = 2
|
||||
}
|
||||
|
||||
|
||||
struct GitIgnoreFile
|
||||
{
|
||||
public string Path { get; private set; }
|
||||
public string[] Contents { get; private set; }
|
||||
|
||||
|
||||
public GitIgnoreFile(string path)
|
||||
{
|
||||
Path = path.Substring(Utility.GitRoot.Length + 1);
|
||||
Contents = File.ReadAllLines(path).Select(l => l.Trim()).Where(l => !string.IsNullOrEmpty(l)).ToArray();
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}:\n{1}", Path, string.Join("\n", Contents));
|
||||
}
|
||||
}
|
||||
private const string GitIgnoreFilePattern = ".gitignore";
|
||||
private const string VCSPropertyName = "m_ExternalVersionControlSupport";
|
||||
private const string SerializationPropertyName = "m_SerializationMode";
|
||||
private const string VisibleMetaFilesValue = "Visible Meta Files";
|
||||
private const string HiddenMetaFilesValue = "Hidden Meta Files";
|
||||
|
||||
private const int ThreadSyncDelay = 100;
|
||||
|
||||
public static string EditorSettingsPath = "ProjectSettings/EditorSettings.asset";
|
||||
|
||||
private static Action<IEnumerable<ProjectConfigurationIssue>> onEvaluationResult;
|
||||
|
||||
const string
|
||||
GitIgnoreFilePattern = ".gitignore",
|
||||
VCSPropertyName = "m_ExternalVersionControlSupport",
|
||||
SerializationPropertyName = "m_SerializationMode",
|
||||
VisibleMetaFilesValue = "Visible Meta Files",
|
||||
HiddenMetaFilesValue = "Hidden Meta Files";
|
||||
|
||||
const int ThreadSyncDelay = 100;
|
||||
|
||||
|
||||
static Action<IEnumerable<ProjectConfigurationIssue>> onEvaluationResult;
|
||||
|
||||
private readonly List<ProjectConfigurationIssue> issues = new List<ProjectConfigurationIssue>();
|
||||
|
||||
public static void RegisterCallback(Action<IEnumerable<ProjectConfigurationIssue>> callback)
|
||||
{
|
||||
onEvaluationResult += callback;
|
||||
}
|
||||
|
||||
|
||||
public static void UnregisterCallback(Action<IEnumerable<ProjectConfigurationIssue>> callback)
|
||||
{
|
||||
onEvaluationResult -= callback;
|
||||
}
|
||||
|
||||
|
||||
public static void Schedule()
|
||||
{
|
||||
Tasks.Add(new EvaluateProjectConfigurationTask());
|
||||
}
|
||||
|
||||
|
||||
public static Object LoadEditorSettings()
|
||||
{
|
||||
return UnityEditorInternal.InternalEditorUtility.LoadSerializedFileAndForget(EditorSettingsPath).FirstOrDefault();
|
||||
return InternalEditorUtility.LoadSerializedFileAndForget(EditorSettingsPath).FirstOrDefault();
|
||||
}
|
||||
|
||||
|
||||
List<ProjectConfigurationIssue> issues = new List<ProjectConfigurationIssue>();
|
||||
|
||||
|
||||
public bool Blocking { get { return false; } }
|
||||
public float Progress { get; protected set; }
|
||||
public bool Done { get; protected set; }
|
||||
public TaskQueueSetting Queued { get { return TaskQueueSetting.QueueSingle; } }
|
||||
public bool Critical { get { return false; } }
|
||||
public bool Cached { get { return false; } }
|
||||
public Action<ITask> OnBegin { set; protected get; }
|
||||
public Action<ITask> OnEnd { set; protected get; }
|
||||
public string Label { get { return "Project Evaluation"; } }
|
||||
|
||||
|
||||
public void Run()
|
||||
{
|
||||
Done = false;
|
||||
|
@ -280,7 +210,7 @@ namespace GitHub.Unity
|
|||
|
||||
issues.Clear();
|
||||
|
||||
if(OnBegin != null)
|
||||
if (OnBegin != null)
|
||||
{
|
||||
OnBegin(this);
|
||||
}
|
||||
|
@ -292,12 +222,15 @@ namespace GitHub.Unity
|
|||
EvaluateGitIgnore();
|
||||
|
||||
// Wait for main thread work to complete
|
||||
while(!Done) { Thread.Sleep(ThreadSyncDelay); }
|
||||
while (!Done)
|
||||
{
|
||||
Thread.Sleep(ThreadSyncDelay);
|
||||
}
|
||||
|
||||
Progress = 1f;
|
||||
Done = true;
|
||||
|
||||
if(OnEnd != null)
|
||||
if (OnEnd != null)
|
||||
{
|
||||
OnEnd(this);
|
||||
}
|
||||
|
@ -308,26 +241,40 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void EvaluateLocalConfiguration()
|
||||
public void Abort()
|
||||
{
|
||||
ProjectSettingsEvaluation result = ProjectSettingsEvaluation.None;
|
||||
Done = true;
|
||||
}
|
||||
|
||||
Object settingsAsset = LoadEditorSettings();
|
||||
public void Disconnect()
|
||||
{}
|
||||
|
||||
public void Reconnect()
|
||||
{}
|
||||
|
||||
public void WriteCache(TextWriter cache)
|
||||
{}
|
||||
|
||||
private void EvaluateLocalConfiguration()
|
||||
{
|
||||
var result = ProjectSettingsEvaluation.None;
|
||||
|
||||
var settingsAsset = LoadEditorSettings();
|
||||
if (settingsAsset == null)
|
||||
{
|
||||
result |= ProjectSettingsEvaluation.EditorSettingsMissing;
|
||||
return;
|
||||
}
|
||||
SerializedObject settingsObject = new SerializedObject(settingsAsset);
|
||||
|
||||
string vcsSetting = settingsObject.FindProperty(VCSPropertyName).stringValue;
|
||||
var settingsObject = new SerializedObject(settingsAsset);
|
||||
|
||||
var vcsSetting = settingsObject.FindProperty(VCSPropertyName).stringValue;
|
||||
if (!vcsSetting.Equals(VisibleMetaFilesValue) && !vcsSetting.Equals(HiddenMetaFilesValue))
|
||||
{
|
||||
result |= ProjectSettingsEvaluation.BadVCSSettings;
|
||||
}
|
||||
|
||||
SerializationSetting serializationSetting = (SerializationSetting)settingsObject.FindProperty(SerializationPropertyName).intValue;
|
||||
var serializationSetting = (SerializationSetting)settingsObject.FindProperty(SerializationPropertyName).intValue;
|
||||
if (serializationSetting == SerializationSetting.ForceBinary)
|
||||
{
|
||||
result |= ProjectSettingsEvaluation.BinarySerialization;
|
||||
|
@ -345,12 +292,11 @@ namespace GitHub.Unity
|
|||
Done = true;
|
||||
}
|
||||
|
||||
|
||||
void EvaluateGitIgnore()
|
||||
private void EvaluateGitIgnore()
|
||||
{
|
||||
// Read rules
|
||||
List<GitIgnoreRule> rules = new List<GitIgnoreRule>(GitIgnoreRule.Count);
|
||||
for (int index = 0; index < rules.Capacity; ++index)
|
||||
var rules = new List<GitIgnoreRule>(GitIgnoreRule.Count);
|
||||
for (var index = 0; index < rules.Capacity; ++index)
|
||||
{
|
||||
GitIgnoreRule rule;
|
||||
if (GitIgnoreRule.TryLoad(index, out rule))
|
||||
|
@ -368,7 +314,10 @@ namespace GitHub.Unity
|
|||
GitIgnoreFile[] files;
|
||||
try
|
||||
{
|
||||
files = Directory.GetFiles(Utility.GitRoot, GitIgnoreFilePattern, SearchOption.AllDirectories).Select(p => new GitIgnoreFile(p)).ToArray();
|
||||
files =
|
||||
Directory.GetFiles(Utility.GitRoot, GitIgnoreFilePattern, SearchOption.AllDirectories)
|
||||
.Select(p => new GitIgnoreFile(p))
|
||||
.ToArray();
|
||||
|
||||
if (files.Length < 1)
|
||||
{
|
||||
|
@ -383,12 +332,12 @@ namespace GitHub.Unity
|
|||
}
|
||||
|
||||
// Evaluate each rule
|
||||
for (int ruleIndex = 0; ruleIndex < rules.Count; ++ruleIndex)
|
||||
for (var ruleIndex = 0; ruleIndex < rules.Count; ++ruleIndex)
|
||||
{
|
||||
GitIgnoreRule rule = rules[ruleIndex];
|
||||
for (int fileIndex = 0; fileIndex < files.Length; ++fileIndex)
|
||||
var rule = rules[ruleIndex];
|
||||
for (var fileIndex = 0; fileIndex < files.Length; ++fileIndex)
|
||||
{
|
||||
GitIgnoreFile file = files[fileIndex];
|
||||
var file = files[fileIndex];
|
||||
// Check against all files with matching path
|
||||
if (rule.File == null || !rule.File.IsMatch(file.Path))
|
||||
{
|
||||
|
@ -396,27 +345,26 @@ namespace GitHub.Unity
|
|||
}
|
||||
|
||||
// Validate all lines in that file
|
||||
for (int lineIndex = 0; lineIndex < file.Contents.Length; ++lineIndex)
|
||||
for (var lineIndex = 0; lineIndex < file.Contents.Length; ++lineIndex)
|
||||
{
|
||||
string line = file.Contents[lineIndex];
|
||||
bool match = rule.Line != null && rule.Line.IsMatch(line);
|
||||
bool broken = false;
|
||||
var line = file.Contents[lineIndex];
|
||||
var match = rule.Line != null && rule.Line.IsMatch(line);
|
||||
|
||||
if (rule.Effect == GitIgnoreRuleEffect.Disallow && match)
|
||||
// This line is not allowed
|
||||
// This line is not allowed
|
||||
{
|
||||
issues.Add(new GitIgnoreIssue(file.Path, line, rule.TriggerText));
|
||||
}
|
||||
else if (rule.Effect == GitIgnoreRuleEffect.Require)
|
||||
// If the line is required, see if we're there
|
||||
// If the line is required, see if we're there
|
||||
{
|
||||
if (match)
|
||||
// We found it! No sense in searching further in this file.
|
||||
// We found it! No sense in searching further in this file.
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (lineIndex == file.Contents.Length - 1)
|
||||
// We reached the last line without finding it
|
||||
// We reached the last line without finding it
|
||||
{
|
||||
issues.Add(new GitIgnoreIssue(file.Path, string.Empty, rule.TriggerText));
|
||||
}
|
||||
|
@ -426,15 +374,59 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public void Abort()
|
||||
public bool Blocking
|
||||
{
|
||||
Done = true;
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public float Progress { get; protected set; }
|
||||
public bool Done { get; protected set; }
|
||||
|
||||
public void Disconnect() {}
|
||||
public void Reconnect() {}
|
||||
public void WriteCache(TextWriter cache) {}
|
||||
public TaskQueueSetting Queued
|
||||
{
|
||||
get { return TaskQueueSetting.QueueSingle; }
|
||||
}
|
||||
|
||||
public bool Critical
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public bool Cached
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public Action<ITask> OnBegin { set; protected get; }
|
||||
public Action<ITask> OnEnd { set; protected get; }
|
||||
|
||||
public string Label
|
||||
{
|
||||
get { return "Project Evaluation"; }
|
||||
}
|
||||
|
||||
private enum SerializationSetting
|
||||
{
|
||||
Mixed = 0,
|
||||
ForceBinary = 1,
|
||||
ForceText = 2
|
||||
}
|
||||
|
||||
private struct GitIgnoreFile
|
||||
{
|
||||
public string Path { get; private set; }
|
||||
public string[] Contents { get; private set; }
|
||||
|
||||
public GitIgnoreFile(string path)
|
||||
{
|
||||
Path = path.Substring(Utility.GitRoot.Length + 1);
|
||||
Contents = File.ReadAllLines(path).Select(l => l.Trim()).Where(l => !string.IsNullOrEmpty(l)).ToArray();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Format("{0}:{1}{2}", Path, Environment.NewLine, string.Join(Environment.NewLine, Contents));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,18 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
class FindGitTask : ProcessTask
|
||||
{
|
||||
public static string DefaultGitPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return Utility.IsWindows ?
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Local\\GitHub\\PortableGit_\\cmd\\git.exe")
|
||||
:
|
||||
"/usr/bin/git";
|
||||
}
|
||||
}
|
||||
private Action onFailure;
|
||||
private Action<string> onSuccess;
|
||||
|
||||
private FindGitTask(Action<string> onSuccess, Action onFailure = null)
|
||||
{
|
||||
this.onSuccess = onSuccess;
|
||||
this.onFailure = onFailure;
|
||||
}
|
||||
|
||||
public static bool ValidateGitInstall(string path)
|
||||
{
|
||||
|
@ -31,39 +24,11 @@ namespace GitHub.Unity
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static void Schedule(Action<string> onSuccess, Action onFailure = null)
|
||||
{
|
||||
Tasks.Add(new FindGitTask(onSuccess, onFailure));
|
||||
}
|
||||
|
||||
|
||||
Action<string> onSuccess;
|
||||
Action onFailure;
|
||||
StringWriter
|
||||
output = new StringWriter(),
|
||||
error = new StringWriter();
|
||||
|
||||
|
||||
FindGitTask(Action<string> onSuccess, Action onFailure = null)
|
||||
{
|
||||
this.onSuccess = onSuccess;
|
||||
this.onFailure = onFailure;
|
||||
}
|
||||
|
||||
|
||||
public override bool Blocking { get { return false; } }
|
||||
public override bool Critical { get { return false; } }
|
||||
public override bool Cached { get { return false; } }
|
||||
public override string Label { get { return "find git"; } }
|
||||
|
||||
|
||||
protected override string ProcessName { get { return Utility.IsWindows ? "where" : "which"; } }
|
||||
protected override string ProcessArguments { get { return "git"; } }
|
||||
protected override TextWriter OutputBuffer { get { return output; } }
|
||||
protected override TextWriter ErrorBuffer { get { return error; } }
|
||||
|
||||
|
||||
protected override void OnProcessOutputUpdate()
|
||||
{
|
||||
if (!Done)
|
||||
|
@ -71,7 +36,7 @@ namespace GitHub.Unity
|
|||
return;
|
||||
}
|
||||
|
||||
StringBuilder buffer = error.GetStringBuilder();
|
||||
var buffer = ErrorBuffer.GetStringBuilder();
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
Tasks.ReportFailure(FailureSeverity.Critical, this, buffer.ToString());
|
||||
|
@ -83,8 +48,49 @@ namespace GitHub.Unity
|
|||
}
|
||||
else if (onSuccess != null)
|
||||
{
|
||||
Tasks.ScheduleMainThread(() => onSuccess(output.ToString().Trim()));
|
||||
Tasks.ScheduleMainThread(() => onSuccess(OutputBuffer.ToString().Trim()));
|
||||
}
|
||||
}
|
||||
|
||||
public static string DefaultGitPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return Utility.IsWindows
|
||||
? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"Local\\GitHub\\PortableGit_\\cmd\\git.exe")
|
||||
: "/usr/bin/git";
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Blocking
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool Critical
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool Cached
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override string Label
|
||||
{
|
||||
get { return "find git"; }
|
||||
}
|
||||
|
||||
protected override string ProcessName
|
||||
{
|
||||
get { return Utility.IsWindows ? "where" : "which"; }
|
||||
}
|
||||
|
||||
protected override string ProcessArguments
|
||||
{
|
||||
get { return "git"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,44 +1,21 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
class GitAddTask : GitTask
|
||||
{
|
||||
public static void Schedule(IEnumerable<string> files, Action onSuccess = null, Action onFailure = null)
|
||||
{
|
||||
Tasks.Add(new GitAddTask(files, onSuccess, onFailure));
|
||||
}
|
||||
private string arguments = "";
|
||||
private Action onFailure;
|
||||
private Action onSuccess;
|
||||
|
||||
|
||||
StringWriter error = new StringWriter();
|
||||
string arguments = "";
|
||||
Action
|
||||
onSuccess,
|
||||
onFailure;
|
||||
|
||||
|
||||
public override bool Blocking { get { return false; } }
|
||||
public override bool Critical { get { return true; } }
|
||||
public override bool Cached { get { return true; } }
|
||||
public override string Label { get { return "git add"; } }
|
||||
|
||||
|
||||
protected override string ProcessArguments { get { return arguments; } }
|
||||
protected override TextWriter ErrorBuffer { get { return error; } }
|
||||
|
||||
|
||||
GitAddTask(IEnumerable<string> files, Action onSuccess = null, Action onFailure = null)
|
||||
private GitAddTask(IEnumerable<string> files, Action onSuccess = null, Action onFailure = null)
|
||||
{
|
||||
arguments = "add ";
|
||||
arguments += " -- ";
|
||||
|
||||
foreach (string file in files)
|
||||
foreach (var file in files)
|
||||
{
|
||||
arguments += " " + file;
|
||||
}
|
||||
|
@ -47,6 +24,10 @@ namespace GitHub.Unity
|
|||
this.onFailure = onFailure;
|
||||
}
|
||||
|
||||
public static void Schedule(IEnumerable<string> files, Action onSuccess = null, Action onFailure = null)
|
||||
{
|
||||
Tasks.Add(new GitAddTask(files, onSuccess, onFailure));
|
||||
}
|
||||
|
||||
protected override void OnProcessOutputUpdate()
|
||||
{
|
||||
|
@ -56,7 +37,7 @@ namespace GitHub.Unity
|
|||
}
|
||||
|
||||
// Handle failure / success
|
||||
StringBuilder buffer = error.GetStringBuilder();
|
||||
var buffer = ErrorBuffer.GetStringBuilder();
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
Tasks.ReportFailure(FailureSeverity.Critical, this, buffer.ToString());
|
||||
|
@ -74,5 +55,30 @@ namespace GitHub.Unity
|
|||
// Always update
|
||||
GitStatusTask.Schedule();
|
||||
}
|
||||
|
||||
public override bool Blocking
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool Critical
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override bool Cached
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override string Label
|
||||
{
|
||||
get { return "git add"; }
|
||||
}
|
||||
|
||||
protected override string ProcessArguments
|
||||
{
|
||||
get { return arguments; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,43 +1,16 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
class GitBranchCreateTask : GitTask
|
||||
{
|
||||
public static void Schedule(string newBranch, string baseBranch, Action onSuccess, Action onFailure = null)
|
||||
{
|
||||
Tasks.Add(new GitBranchCreateTask(newBranch, baseBranch, onSuccess, onFailure));
|
||||
}
|
||||
private string baseBranch;
|
||||
private string newBranch;
|
||||
private Action onFailure;
|
||||
private Action onSuccess;
|
||||
|
||||
|
||||
string
|
||||
newBranch,
|
||||
baseBranch;
|
||||
Action
|
||||
onSuccess,
|
||||
onFailure;
|
||||
StringWriter
|
||||
output = new StringWriter(),
|
||||
error = new StringWriter();
|
||||
|
||||
|
||||
public override bool Blocking { get { return false; } }
|
||||
public override TaskQueueSetting Queued { get { return TaskQueueSetting.Queue; } }
|
||||
public override bool Critical { get { return false; } }
|
||||
public override bool Cached { get { return true; } }
|
||||
public override string Label { get { return "git branch"; } }
|
||||
|
||||
protected override string ProcessArguments { get { return string.Format("branch {0} {1}", newBranch, baseBranch); } }
|
||||
protected override TextWriter OutputBuffer { get { return output; } }
|
||||
protected override TextWriter ErrorBuffer { get { return error; } }
|
||||
|
||||
|
||||
GitBranchCreateTask(string newBranch, string baseBranch, Action onSuccess, Action onFailure)
|
||||
private GitBranchCreateTask(string newBranch, string baseBranch, Action onSuccess, Action onFailure)
|
||||
{
|
||||
this.newBranch = newBranch;
|
||||
this.baseBranch = baseBranch;
|
||||
|
@ -45,13 +18,17 @@ namespace GitHub.Unity
|
|||
this.onFailure = onFailure;
|
||||
}
|
||||
|
||||
public static void Schedule(string newBranch, string baseBranch, Action onSuccess, Action onFailure = null)
|
||||
{
|
||||
Tasks.Add(new GitBranchCreateTask(newBranch, baseBranch, onSuccess, onFailure));
|
||||
}
|
||||
|
||||
protected override void OnProcessOutputUpdate()
|
||||
{
|
||||
if (Done)
|
||||
{
|
||||
// Handle failure / success
|
||||
StringBuilder buffer = error.GetStringBuilder();
|
||||
var buffer = ErrorBuffer.GetStringBuilder();
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
Tasks.ReportFailure(FailureSeverity.Critical, this, buffer.ToString());
|
||||
|
@ -69,5 +46,35 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Blocking
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override TaskQueueSetting Queued
|
||||
{
|
||||
get { return TaskQueueSetting.Queue; }
|
||||
}
|
||||
|
||||
public override bool Critical
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool Cached
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override string Label
|
||||
{
|
||||
get { return "git branch"; }
|
||||
}
|
||||
|
||||
protected override string ProcessArguments
|
||||
{
|
||||
get { return String.Format("branch {0} {1}", newBranch, baseBranch); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,53 +1,33 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
class GitCommitTask : GitTask
|
||||
{
|
||||
public static void Schedule(IEnumerable<string> files, string message, string body, Action onSuccess = null, Action onFailure = null)
|
||||
{
|
||||
GitAddTask.Schedule(files, () => Schedule(message, body, onSuccess, onFailure), onFailure);
|
||||
}
|
||||
private string arguments = "";
|
||||
private Action onFailure;
|
||||
private Action onSuccess;
|
||||
|
||||
|
||||
public static void Schedule(string message, string body, Action onSuccess = null, Action onFailure = null)
|
||||
{
|
||||
Tasks.Add(new GitCommitTask(message, body, onSuccess, onFailure));
|
||||
}
|
||||
|
||||
|
||||
StringWriter error = new StringWriter();
|
||||
string arguments = "";
|
||||
Action
|
||||
onSuccess,
|
||||
onFailure;
|
||||
|
||||
|
||||
GitCommitTask(string message, string body, Action onSuccess = null, Action onFailure = null)
|
||||
private GitCommitTask(string message, string body, Action onSuccess = null, Action onFailure = null)
|
||||
{
|
||||
arguments = "commit ";
|
||||
arguments += string.Format(@" -m ""{0}{1}{2}""", message, Environment.NewLine, body);
|
||||
arguments += String.Format(@" -m ""{0}{1}{2}""", message, Environment.NewLine, body);
|
||||
|
||||
this.onSuccess = onSuccess;
|
||||
this.onFailure = onFailure;
|
||||
}
|
||||
|
||||
public static void Schedule(IEnumerable<string> files, string message, string body, Action onSuccess = null, Action onFailure = null)
|
||||
{
|
||||
GitAddTask.Schedule(files, () => Schedule(message, body, onSuccess, onFailure), onFailure);
|
||||
}
|
||||
|
||||
public override bool Blocking { get { return false; } }
|
||||
public override bool Critical { get { return true; } }
|
||||
public override bool Cached { get { return true; } }
|
||||
public override string Label { get { return "git commit"; } }
|
||||
|
||||
|
||||
protected override string ProcessArguments { get { return arguments; } }
|
||||
protected override TextWriter ErrorBuffer { get { return error; } }
|
||||
|
||||
public static void Schedule(string message, string body, Action onSuccess = null, Action onFailure = null)
|
||||
{
|
||||
Tasks.Add(new GitCommitTask(message, body, onSuccess, onFailure));
|
||||
}
|
||||
|
||||
protected override void OnProcessOutputUpdate()
|
||||
{
|
||||
|
@ -57,7 +37,7 @@ namespace GitHub.Unity
|
|||
}
|
||||
|
||||
// Handle failure / success
|
||||
StringBuilder buffer = error.GetStringBuilder();
|
||||
var buffer = ErrorBuffer.GetStringBuilder();
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
Tasks.ReportFailure(FailureSeverity.Critical, this, buffer.ToString());
|
||||
|
@ -75,5 +55,30 @@ namespace GitHub.Unity
|
|||
// Always update
|
||||
GitStatusTask.Schedule();
|
||||
}
|
||||
|
||||
public override bool Blocking
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool Critical
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override bool Cached
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override string Label
|
||||
{
|
||||
get { return "git commit"; }
|
||||
}
|
||||
|
||||
protected override string ProcessArguments
|
||||
{
|
||||
get { return arguments; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,14 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Linq;
|
||||
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
struct GitBranch
|
||||
{
|
||||
public string Name{ get; private set; }
|
||||
public string Tracking{ get; private set; }
|
||||
public bool Active{ get; private set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
public string Tracking { get; private set; }
|
||||
public bool Active { get; private set; }
|
||||
|
||||
public GitBranch(string name, string tracking, bool active)
|
||||
{
|
||||
|
@ -25,78 +18,46 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class GitListBranchesTask : GitTask
|
||||
{
|
||||
enum Mode
|
||||
{
|
||||
Local,
|
||||
Remote
|
||||
}
|
||||
private const string LocalArguments = "branch -vv";
|
||||
private const string RemoteArguments = "branch -r";
|
||||
private const string UnmatchedLineError = "Unable to match the line '{0}'";
|
||||
private List<GitBranch> branches = new List<GitBranch>();
|
||||
private Mode mode;
|
||||
private Action onFailure;
|
||||
private Action<IEnumerable<GitBranch>> onSuccess;
|
||||
|
||||
|
||||
const string
|
||||
LocalArguments = "branch -vv",
|
||||
RemoteArguments = "branch -r",
|
||||
UnmatchedLineError = "Unable to match the line '{0}'";
|
||||
|
||||
|
||||
public static void ScheduleLocal(Action<IEnumerable<GitBranch>> onSuccess, Action onFailure = null)
|
||||
{
|
||||
Schedule(Mode.Local, onSuccess, onFailure);
|
||||
}
|
||||
|
||||
|
||||
public static void ScheduleRemote(Action<IEnumerable<GitBranch>> onSuccess, Action onFailure = null)
|
||||
{
|
||||
Schedule(Mode.Remote, onSuccess, onFailure);
|
||||
}
|
||||
|
||||
|
||||
static void Schedule(Mode mode, Action<IEnumerable<GitBranch>> onSuccess, Action onFailure = null)
|
||||
{
|
||||
Tasks.Add(new GitListBranchesTask(mode, onSuccess, onFailure));
|
||||
}
|
||||
|
||||
|
||||
StringWriter
|
||||
output = new StringWriter(),
|
||||
error = new StringWriter();
|
||||
Mode mode;
|
||||
List<GitBranch> branches = new List<GitBranch>();
|
||||
int activeIndex;
|
||||
Action<IEnumerable<GitBranch>> onSuccess;
|
||||
Action onFailure;
|
||||
|
||||
|
||||
public override bool Blocking { get { return false; } }
|
||||
public override TaskQueueSetting Queued { get { return TaskQueueSetting.Queue; } }
|
||||
public override bool Critical { get { return false; } }
|
||||
public override bool Cached { get { return false; } }
|
||||
public override string Label { get { return "git branch"; } }
|
||||
|
||||
|
||||
protected override string ProcessArguments { get { return mode == Mode.Local ? LocalArguments : RemoteArguments; } }
|
||||
protected override TextWriter OutputBuffer { get { return output; } }
|
||||
protected override TextWriter ErrorBuffer { get { return error; } }
|
||||
|
||||
|
||||
GitListBranchesTask(Mode mode, Action<IEnumerable<GitBranch>> onSuccess, Action onFailure = null)
|
||||
private GitListBranchesTask(Mode mode, Action<IEnumerable<GitBranch>> onSuccess, Action onFailure = null)
|
||||
{
|
||||
this.mode = mode;
|
||||
this.onSuccess = onSuccess;
|
||||
this.onFailure = onFailure;
|
||||
}
|
||||
|
||||
public static void ScheduleLocal(Action<IEnumerable<GitBranch>> onSuccess, Action onFailure = null)
|
||||
{
|
||||
Schedule(Mode.Local, onSuccess, onFailure);
|
||||
}
|
||||
|
||||
public static void ScheduleRemote(Action<IEnumerable<GitBranch>> onSuccess, Action onFailure = null)
|
||||
{
|
||||
Schedule(Mode.Remote, onSuccess, onFailure);
|
||||
}
|
||||
|
||||
private static void Schedule(Mode mode, Action<IEnumerable<GitBranch>> onSuccess, Action onFailure = null)
|
||||
{
|
||||
Tasks.Add(new GitListBranchesTask(mode, onSuccess, onFailure));
|
||||
}
|
||||
|
||||
protected override void OnProcessOutputUpdate()
|
||||
{
|
||||
Utility.ParseLines(output.GetStringBuilder(), ParseOutputLine, Done);
|
||||
Utility.ParseLines(OutputBuffer.GetStringBuilder(), ParseOutputLine, Done);
|
||||
|
||||
if (Done)
|
||||
{
|
||||
// Handle failure / success
|
||||
StringBuilder buffer = error.GetStringBuilder();
|
||||
var buffer = ErrorBuffer.GetStringBuilder();
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
Tasks.ReportFailure(FailureSeverity.Moderate, this, buffer.ToString());
|
||||
|
@ -112,8 +73,7 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void DeliverResult()
|
||||
private void DeliverResult()
|
||||
{
|
||||
if (onSuccess == null)
|
||||
{
|
||||
|
@ -123,18 +83,54 @@ namespace GitHub.Unity
|
|||
onSuccess(branches);
|
||||
}
|
||||
|
||||
|
||||
void ParseOutputLine(string line)
|
||||
private void ParseOutputLine(string line)
|
||||
{
|
||||
Match match = Utility.ListBranchesRegex.Match(line);
|
||||
var match = Utility.ListBranchesRegex.Match(line);
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
Tasks.ReportFailure(FailureSeverity.Moderate, this, string.Format(UnmatchedLineError, line));
|
||||
Tasks.ReportFailure(FailureSeverity.Moderate, this, String.Format(UnmatchedLineError, line));
|
||||
return;
|
||||
}
|
||||
|
||||
branches.Add(new GitBranch(match.Groups["name"].Value, match.Groups["tracking"].Value, !string.IsNullOrEmpty(match.Groups["active"].Value)));
|
||||
branches.Add(new GitBranch(match.Groups["name"].Value, match.Groups["tracking"].Value,
|
||||
!string.IsNullOrEmpty(match.Groups["active"].Value)));
|
||||
}
|
||||
|
||||
public override bool Blocking
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override TaskQueueSetting Queued
|
||||
{
|
||||
get { return TaskQueueSetting.Queue; }
|
||||
}
|
||||
|
||||
public override bool Critical
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool Cached
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override string Label
|
||||
{
|
||||
get { return "git branch"; }
|
||||
}
|
||||
|
||||
protected override string ProcessArguments
|
||||
{
|
||||
get { return mode == Mode.Local ? LocalArguments : RemoteArguments; }
|
||||
}
|
||||
|
||||
private enum Mode
|
||||
{
|
||||
Local,
|
||||
Remote
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
|
@ -17,12 +14,19 @@ namespace GitHub.Unity
|
|||
Both
|
||||
}
|
||||
|
||||
|
||||
struct GitRemote
|
||||
{
|
||||
public string Name;
|
||||
public string URL;
|
||||
public string Login;
|
||||
public string User;
|
||||
public string Token;
|
||||
public string Host;
|
||||
public GitRemoteFunction Function;
|
||||
|
||||
public static bool TryParse(string line, out GitRemote result)
|
||||
{
|
||||
Match match = Utility.ListRemotesRegex.Match(line);
|
||||
var match = Utility.ListRemotesRegex.Match(line);
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
|
@ -30,8 +34,7 @@ namespace GitHub.Unity
|
|||
return false;
|
||||
}
|
||||
|
||||
result = new GitRemote()
|
||||
{
|
||||
result = new GitRemote() {
|
||||
Name = match.Groups["name"].Value,
|
||||
URL = match.Groups["url"].Value,
|
||||
Login = match.Groups["login"].Value,
|
||||
|
@ -44,97 +47,56 @@ namespace GitHub.Unity
|
|||
{
|
||||
result.Function = (GitRemoteFunction)Enum.Parse(typeof(GitRemoteFunction), match.Groups["function"].Value, true);
|
||||
}
|
||||
catch(Exception)
|
||||
catch (Exception)
|
||||
{}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public string
|
||||
Name,
|
||||
URL,
|
||||
Login,
|
||||
User,
|
||||
Token,
|
||||
Host;
|
||||
public GitRemoteFunction Function;
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(
|
||||
@"Name: {0}
|
||||
URL: {1}
|
||||
Login: {2}
|
||||
User: {3}
|
||||
Token: {4}
|
||||
Host: {5}
|
||||
Function: {6}",
|
||||
Name,
|
||||
URL,
|
||||
Login,
|
||||
User,
|
||||
Token,
|
||||
Host,
|
||||
Function
|
||||
);
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(String.Format("Name: {0}", Name));
|
||||
sb.AppendLine(String.Format("URL: {0}", URL));
|
||||
sb.AppendLine(String.Format("Login: {0}", Login));
|
||||
sb.AppendLine(String.Format("User: {0}", User));
|
||||
sb.AppendLine(String.Format("Token: {0}", Token));
|
||||
sb.AppendLine(String.Format("Host: {0}", Host));
|
||||
sb.AppendLine(String.Format("Function: {0}", Function));
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class GitListRemotesTask : GitTask
|
||||
{
|
||||
const string ParseFailedError = "Remote parse error in line: '{0}'";
|
||||
|
||||
|
||||
static Action<IList<GitRemote>> onRemotesListed;
|
||||
private const string ParseFailedError = "Remote parse error in line: '{0}'";
|
||||
|
||||
private static Action<IList<GitRemote>> onRemotesListed;
|
||||
private List<GitRemote> entries = new List<GitRemote>();
|
||||
|
||||
public static void RegisterCallback(Action<IList<GitRemote>> callback)
|
||||
{
|
||||
onRemotesListed += callback;
|
||||
}
|
||||
|
||||
|
||||
public static void UnregisterCallback(Action<IList<GitRemote>> callback)
|
||||
{
|
||||
onRemotesListed -= callback;
|
||||
}
|
||||
|
||||
|
||||
public static void Schedule()
|
||||
{
|
||||
Tasks.Add(new GitListRemotesTask());
|
||||
}
|
||||
|
||||
|
||||
StringWriter
|
||||
output = new StringWriter(),
|
||||
error = new StringWriter();
|
||||
List<GitRemote> entries = new List<GitRemote>();
|
||||
|
||||
|
||||
public override bool Blocking { get { return false; } }
|
||||
public virtual TaskQueueSetting Queued { get { return TaskQueueSetting.QueueSingle; } }
|
||||
public override bool Critical { get { return false; } }
|
||||
public override bool Cached { get { return false; } }
|
||||
public override string Label { get { return "git remote"; } }
|
||||
|
||||
|
||||
protected override string ProcessArguments { get { return "remote -v"; } }
|
||||
protected override TextWriter OutputBuffer { get { return output; } }
|
||||
protected override TextWriter ErrorBuffer { get { return error; } }
|
||||
|
||||
|
||||
protected override void OnProcessOutputUpdate()
|
||||
{
|
||||
Utility.ParseLines(output.GetStringBuilder(), ParseOutputLine, Done);
|
||||
Utility.ParseLines(OutputBuffer.GetStringBuilder(), ParseOutputLine, Done);
|
||||
|
||||
if (Done)
|
||||
{
|
||||
// Handle failure / success
|
||||
StringBuilder buffer = error.GetStringBuilder();
|
||||
var buffer = ErrorBuffer.GetStringBuilder();
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
Tasks.ReportFailure(FailureSeverity.Moderate, this, buffer.ToString());
|
||||
|
@ -146,8 +108,7 @@ Function: {6}",
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void DeliverResult()
|
||||
private void DeliverResult()
|
||||
{
|
||||
if (onRemotesListed != null)
|
||||
{
|
||||
|
@ -157,20 +118,16 @@ Function: {6}",
|
|||
entries.Clear();
|
||||
}
|
||||
|
||||
|
||||
void ParseOutputLine(string line)
|
||||
private void ParseOutputLine(string line)
|
||||
{
|
||||
// Parse line as a remote
|
||||
GitRemote remote;
|
||||
if (GitRemote.TryParse(line, out remote))
|
||||
{
|
||||
// Join Fetch/Push entries into single Both entries
|
||||
if (
|
||||
remote.Function != GitRemoteFunction.Unknown &&
|
||||
entries.RemoveAll(e =>
|
||||
e.Function != GitRemoteFunction.Unknown && e.Function != remote.Function && e.Name.Equals(remote.Name)
|
||||
) > 0
|
||||
)
|
||||
if (remote.Function != GitRemoteFunction.Unknown &&
|
||||
entries.RemoveAll(
|
||||
e => e.Function != GitRemoteFunction.Unknown && e.Function != remote.Function && e.Name.Equals(remote.Name)) > 0)
|
||||
{
|
||||
remote.Function = GitRemoteFunction.Both;
|
||||
}
|
||||
|
@ -183,5 +140,35 @@ Function: {6}",
|
|||
Debug.LogWarningFormat(ParseFailedError, line);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Blocking
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public virtual TaskQueueSetting Queued
|
||||
{
|
||||
get { return TaskQueueSetting.QueueSingle; }
|
||||
}
|
||||
|
||||
public override bool Critical
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool Cached
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override string Label
|
||||
{
|
||||
get { return "git remote"; }
|
||||
}
|
||||
|
||||
protected override string ProcessArguments
|
||||
{
|
||||
get { return "remote -v"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,67 +1,44 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
class GitListUntrackedFilesTask : GitTask
|
||||
{
|
||||
public static void Schedule(Action<GitListUntrackedFilesTask> success, Action failure = null)
|
||||
{
|
||||
Tasks.Add(new GitListUntrackedFilesTask(success, failure));
|
||||
}
|
||||
private List<GitStatusEntry> entries = new List<GitStatusEntry>();
|
||||
private Action onFailure;
|
||||
private Action<GitListUntrackedFilesTask> onSuccess;
|
||||
|
||||
|
||||
public override bool Blocking { get { return false; } }
|
||||
public override bool Critical { get { return false; } }
|
||||
public override bool Cached { get { return false; } }
|
||||
public override string Label { get { return "git ls-files"; } }
|
||||
|
||||
public IList<GitStatusEntry> Entries { get { return entries; } }
|
||||
|
||||
|
||||
protected override string ProcessArguments { get { return "ls-files -o --exclude-standard"; } }
|
||||
protected override TextWriter OutputBuffer { get { return output; } }
|
||||
protected override TextWriter ErrorBuffer { get { return error; } }
|
||||
|
||||
|
||||
Action<GitListUntrackedFilesTask> onSuccess;
|
||||
Action onFailure;
|
||||
StringWriter
|
||||
output = new StringWriter(),
|
||||
error = new StringWriter();
|
||||
List<GitStatusEntry> entries = new List<GitStatusEntry>();
|
||||
|
||||
|
||||
GitListUntrackedFilesTask(Action<GitListUntrackedFilesTask> success, Action failure = null)
|
||||
private GitListUntrackedFilesTask(Action<GitListUntrackedFilesTask> success, Action failure = null)
|
||||
{
|
||||
onSuccess = success;
|
||||
onFailure = failure;
|
||||
}
|
||||
|
||||
public static void Schedule(Action<GitListUntrackedFilesTask> success, Action failure = null)
|
||||
{
|
||||
Tasks.Add(new GitListUntrackedFilesTask(success, failure));
|
||||
}
|
||||
|
||||
protected override void OnProcessOutputUpdate()
|
||||
{
|
||||
StringBuilder buffer = output.GetStringBuilder();
|
||||
int end = buffer.Length - 1;
|
||||
var buffer = OutputBuffer.GetStringBuilder();
|
||||
var end = buffer.Length - 1;
|
||||
|
||||
if(!Done)
|
||||
// Only try to avoid partial lines if the process did not already end
|
||||
if (!Done)
|
||||
{
|
||||
for(; end > 0 && buffer[end] != '\n'; --end);
|
||||
for (; end > 0 && buffer[end] != '\n'; --end) ;
|
||||
}
|
||||
|
||||
if(end > 0)
|
||||
// Parse output lines into the entries list if we have any buffer to parse
|
||||
if (end > 0)
|
||||
{
|
||||
for(int index = 0, last = -1; index <= end; ++index)
|
||||
for (int index = 0, last = -1; index <= end; ++index)
|
||||
{
|
||||
if(buffer[index] == '\n')
|
||||
if (buffer[index] == '\n')
|
||||
{
|
||||
ParseOutputLine(last + 1, index);
|
||||
last = index;
|
||||
|
@ -71,12 +48,13 @@ namespace GitHub.Unity
|
|||
buffer.Remove(0, end + 1);
|
||||
}
|
||||
|
||||
if(Done)
|
||||
// Process results when we are done
|
||||
if (Done)
|
||||
{
|
||||
buffer = error.GetStringBuilder();
|
||||
if (buffer.Length > 0)
|
||||
buffer = ErrorBuffer.GetStringBuilder();
|
||||
|
||||
// We failed. Make a little noise.
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
Tasks.ReportFailure(FailureSeverity.Moderate, this, buffer.ToString());
|
||||
if (onFailure != null)
|
||||
|
@ -84,22 +62,51 @@ namespace GitHub.Unity
|
|||
Tasks.ScheduleMainThread(() => onFailure());
|
||||
}
|
||||
}
|
||||
else if (onSuccess != null)
|
||||
// We succeeded. Hand over the results!
|
||||
else if (onSuccess != null)
|
||||
{
|
||||
Tasks.ScheduleMainThread(() => onSuccess(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ParseOutputLine(int start, int end)
|
||||
private void ParseOutputLine(int start, int end)
|
||||
{
|
||||
string path = output.GetStringBuilder().ToString(start, end - start);
|
||||
var path = OutputBuffer.GetStringBuilder().ToString(start, end - start);
|
||||
if (!entries.Any(e => e.Path.Equals(path)))
|
||||
{
|
||||
entries.Add(new GitStatusEntry(path, GitFileStatus.Untracked));
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Blocking
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool Critical
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool Cached
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override string Label
|
||||
{
|
||||
get { return "git ls-files"; }
|
||||
}
|
||||
|
||||
public IList<GitStatusEntry> Entries
|
||||
{
|
||||
get { return entries; }
|
||||
}
|
||||
|
||||
protected override string ProcessArguments
|
||||
{
|
||||
get { return "ls-files -o --exclude-standard"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,61 +1,40 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
struct GitLogEntry
|
||||
{
|
||||
const string
|
||||
Today = "Today",
|
||||
Yesterday = "Yesterday";
|
||||
private const string Today = "Today";
|
||||
private const string Yesterday = "Yesterday";
|
||||
|
||||
public string
|
||||
CommitID,
|
||||
MergeA,
|
||||
MergeB,
|
||||
AuthorName,
|
||||
AuthorEmail,
|
||||
Summary,
|
||||
Description;
|
||||
public string CommitID, MergeA, MergeB, AuthorName, AuthorEmail, Summary, Description;
|
||||
public DateTimeOffset Time;
|
||||
public List<GitStatusEntry> Changes;
|
||||
|
||||
|
||||
public string ShortID
|
||||
{
|
||||
get
|
||||
{
|
||||
return CommitID.Length < 7 ? CommitID : CommitID.Substring(0, 7);
|
||||
}
|
||||
get { return CommitID.Length < 7 ? CommitID : CommitID.Substring(0, 7); }
|
||||
}
|
||||
|
||||
|
||||
public string PrettyTimeString
|
||||
{
|
||||
get
|
||||
{
|
||||
DateTimeOffset
|
||||
now = DateTimeOffset.Now,
|
||||
relative = Time.ToLocalTime();
|
||||
DateTimeOffset now = DateTimeOffset.Now, relative = Time.ToLocalTime();
|
||||
|
||||
return string.Format(
|
||||
"{0}, {1:HH}:{1:mm}",
|
||||
relative.DayOfYear == now.DayOfYear ? Today :
|
||||
relative.DayOfYear == now.DayOfYear - 1 ? Yesterday :
|
||||
relative.ToString("d MMM yyyy"),
|
||||
relative
|
||||
);
|
||||
return String.Format("{0}, {1:HH}:{1:mm}",
|
||||
relative.DayOfYear == now.DayOfYear
|
||||
? Today
|
||||
: relative.DayOfYear == now.DayOfYear - 1 ? Yesterday : relative.ToString("d MMM yyyy"), relative);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
CommitID = MergeA = MergeB = AuthorName = AuthorEmail = Summary = Description = "";
|
||||
|
@ -63,106 +42,63 @@ namespace GitHub.Unity
|
|||
Changes = new List<GitStatusEntry>();
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(
|
||||
@"CommitID: {0}
|
||||
MergeA: {1}
|
||||
MergeB: {2}
|
||||
AuthorName: {3}
|
||||
AuthorEmail: {4}
|
||||
Time: {5}
|
||||
Summary: {6}
|
||||
Description: {7}",
|
||||
CommitID,
|
||||
MergeA,
|
||||
MergeB,
|
||||
AuthorName,
|
||||
AuthorEmail,
|
||||
Time.ToString(),
|
||||
Summary,
|
||||
Description
|
||||
);
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(String.Format("CommitID: {0}", CommitID));
|
||||
sb.AppendLine(String.Format("MergeA: {0}", MergeA));
|
||||
sb.AppendLine(String.Format("MergeB: {0}", MergeB));
|
||||
sb.AppendLine(String.Format("AuthorName: {0}", AuthorName));
|
||||
sb.AppendLine(String.Format("AuthorEmail: {0}", AuthorEmail));
|
||||
sb.AppendLine(String.Format("Time: {0}", Time.ToString()));
|
||||
sb.AppendLine(String.Format("Summary: {0}", Summary));
|
||||
sb.AppendLine(String.Format("Description: {0}", Description));
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class GitLogTask : GitTask
|
||||
{
|
||||
enum ParsePhase
|
||||
private const string UnhandledParsePhaseError = "Unhandled parse phase: '{0}'";
|
||||
private const string LineParseError = "Log parse error in line: '{0}', parse phase: '{1}'";
|
||||
private const string GitTimeFormat = "ddd MMM d HH:mm:ss yyyy zz";
|
||||
|
||||
private static Action<IList<GitLogEntry>> onLogUpdate;
|
||||
|
||||
private string arguments = "log --name-status";
|
||||
private bool completed = false;
|
||||
private List<GitLogEntry> entries = new List<GitLogEntry>();
|
||||
private GitLogEntry parsedEntry = new GitLogEntry();
|
||||
private ParsePhase parsePhase;
|
||||
|
||||
private GitLogTask(string file = null)
|
||||
{
|
||||
Commit,
|
||||
Author,
|
||||
Time,
|
||||
Description,
|
||||
Changes
|
||||
parsedEntry.Clear();
|
||||
|
||||
if (!string.IsNullOrEmpty(file))
|
||||
{
|
||||
arguments = String.Format("{0} --follow -m {1}", arguments, file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const string
|
||||
UnhandledParsePhaseError = "Unhandled parse phase: '{0}'",
|
||||
LineParseError = "Log parse error in line: '{0}', parse phase: '{1}'",
|
||||
GitTimeFormat = "ddd MMM d HH:mm:ss yyyy zz";
|
||||
|
||||
|
||||
static Action<IList<GitLogEntry>> onLogUpdate;
|
||||
|
||||
|
||||
public static void RegisterCallback(Action<IList<GitLogEntry>> callback)
|
||||
{
|
||||
onLogUpdate += callback;
|
||||
}
|
||||
|
||||
|
||||
public static void UnregisterCallback(Action<IList<GitLogEntry>> callback)
|
||||
{
|
||||
onLogUpdate -= callback;
|
||||
}
|
||||
|
||||
|
||||
public static void Schedule(string file = null)
|
||||
{
|
||||
Tasks.Add(new GitLogTask(file));
|
||||
}
|
||||
|
||||
|
||||
string arguments = "log --name-status";
|
||||
StringWriter
|
||||
output = new StringWriter(),
|
||||
error = new StringWriter();
|
||||
List<GitLogEntry> entries = new List<GitLogEntry>();
|
||||
GitLogEntry parsedEntry = new GitLogEntry();
|
||||
ParsePhase parsePhase;
|
||||
bool completed = false;
|
||||
|
||||
|
||||
public override bool Blocking { get { return false; } }
|
||||
public virtual TaskQueueSetting Queued { get { return TaskQueueSetting.QueueSingle; } }
|
||||
public override bool Critical { get { return false; } }
|
||||
public override bool Cached { get { return false; } }
|
||||
public override string Label { get { return "git log"; } }
|
||||
|
||||
|
||||
protected override string ProcessArguments { get { return arguments; } }
|
||||
protected override TextWriter OutputBuffer { get { return output; } }
|
||||
protected override TextWriter ErrorBuffer { get { return error; } }
|
||||
|
||||
|
||||
GitLogTask(string file = null)
|
||||
{
|
||||
parsedEntry.Clear();
|
||||
|
||||
if (!string.IsNullOrEmpty(file))
|
||||
{
|
||||
arguments = string.Format("{0} --follow -m {1}", arguments, file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void OnProcessOutputUpdate()
|
||||
{
|
||||
Utility.ParseLines(output.GetStringBuilder(), ParseOutputLine, Done);
|
||||
Utility.ParseLines(OutputBuffer.GetStringBuilder(), ParseOutputLine, Done);
|
||||
|
||||
if (Done && !completed)
|
||||
{
|
||||
|
@ -172,7 +108,7 @@ Description: {7}",
|
|||
ParseOutputLine(null);
|
||||
|
||||
// Handle failure / success
|
||||
StringBuilder buffer = error.GetStringBuilder();
|
||||
var buffer = ErrorBuffer.GetStringBuilder();
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
Tasks.ReportFailure(FailureSeverity.Moderate, this, buffer.ToString());
|
||||
|
@ -184,8 +120,7 @@ Description: {7}",
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void DeliverResult()
|
||||
private void DeliverResult()
|
||||
{
|
||||
if (onLogUpdate != null)
|
||||
{
|
||||
|
@ -195,8 +130,7 @@ Description: {7}",
|
|||
entries.Clear();
|
||||
}
|
||||
|
||||
|
||||
void ParseOutputLine(string line)
|
||||
private void ParseOutputLine(string line)
|
||||
{
|
||||
// Empty lines are section or commit dividers
|
||||
if (string.IsNullOrEmpty(line))
|
||||
|
@ -207,10 +141,10 @@ Description: {7}",
|
|||
entries.Add(parsedEntry);
|
||||
parsedEntry.Clear();
|
||||
parsePhase = ParsePhase.Commit;
|
||||
return;
|
||||
return;
|
||||
default:
|
||||
++parsePhase;
|
||||
return;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,7 +160,8 @@ Description: {7}",
|
|||
++parsePhase;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
break;
|
||||
case ParsePhase.Author:
|
||||
// If this is a marge commit, merge info comes before author info, so we parse this and stay in the author phase
|
||||
match = Utility.LogMergeRegex.Match(line);
|
||||
|
@ -245,33 +180,25 @@ Description: {7}",
|
|||
++parsePhase;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
break;
|
||||
case ParsePhase.Time:
|
||||
match = Utility.LogTimeRegex.Match(line);
|
||||
if (match.Groups.Count == 2)
|
||||
{
|
||||
string time = match.Groups[1].ToString();
|
||||
var time = match.Groups[1].ToString();
|
||||
|
||||
parsedEntry.Time = DateTimeOffset.ParseExact(
|
||||
time,
|
||||
GitTimeFormat,
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
System.Globalization.DateTimeStyles.None
|
||||
);
|
||||
parsedEntry.Time = DateTimeOffset.ParseExact(time, GitTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None);
|
||||
|
||||
if (DateTimeOffset.TryParseExact(
|
||||
time,
|
||||
GitTimeFormat,
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.None,
|
||||
out parsedEntry.Time
|
||||
))
|
||||
if (DateTimeOffset.TryParseExact(time, GitTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None,
|
||||
out parsedEntry.Time))
|
||||
{
|
||||
// NOTE: Time is always last in the header, so we should not progress to next phase here - the divider will do that
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
break;
|
||||
case ParsePhase.Description:
|
||||
match = Utility.LogDescriptionRegex.Match(line);
|
||||
if (match.Groups.Count == 2)
|
||||
|
@ -286,31 +213,72 @@ Description: {7}",
|
|||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
break;
|
||||
case ParsePhase.Changes:
|
||||
GitStatusEntry entry;
|
||||
|
||||
if (GitStatusEntry.TryParse(line, out entry))
|
||||
// Try to read the line as a change entry
|
||||
if (GitStatusEntry.TryParse(line, out entry))
|
||||
{
|
||||
parsedEntry.Changes.Add(entry);
|
||||
return;
|
||||
}
|
||||
else if ((match = Utility.LogCommitRegex.Match(line)).Groups.Count == 2)
|
||||
// This commit had no changes, so complete parsing it and pass the next commit header into a new session
|
||||
else if ((match = Utility.LogCommitRegex.Match(line)).Groups.Count == 2)
|
||||
{
|
||||
ParseOutputLine(null);
|
||||
ParseOutputLine(line);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ApplicationException(string.Format(UnhandledParsePhaseError, parsePhase));
|
||||
throw new TaskException(String.Format(UnhandledParsePhaseError, parsePhase));
|
||||
}
|
||||
|
||||
// Garbled input. Eject!
|
||||
Debug.LogErrorFormat(LineParseError, line, parsePhase);
|
||||
Abort();
|
||||
}
|
||||
|
||||
public override bool Blocking
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public virtual TaskQueueSetting Queued
|
||||
{
|
||||
get { return TaskQueueSetting.QueueSingle; }
|
||||
}
|
||||
|
||||
public override bool Critical
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool Cached
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override string Label
|
||||
{
|
||||
get { return "git log"; }
|
||||
}
|
||||
|
||||
protected override string ProcessArguments
|
||||
{
|
||||
get { return arguments; }
|
||||
}
|
||||
|
||||
private enum ParsePhase
|
||||
{
|
||||
Commit,
|
||||
Author,
|
||||
Time,
|
||||
Description,
|
||||
Changes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
|
@ -20,18 +15,12 @@ namespace GitHub.Unity
|
|||
Copied
|
||||
}
|
||||
|
||||
|
||||
struct GitStatus
|
||||
{
|
||||
public string
|
||||
LocalBranch,
|
||||
RemoteBranch;
|
||||
public int
|
||||
Ahead,
|
||||
Behind;
|
||||
public string LocalBranch, RemoteBranch;
|
||||
public int Ahead, Behind;
|
||||
public List<GitStatusEntry> Entries;
|
||||
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
LocalBranch = RemoteBranch = "";
|
||||
|
@ -39,34 +28,22 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
struct GitStatusEntry
|
||||
{
|
||||
const string UnknownStatusKeyError = "Unknown file status key: '{0}'";
|
||||
|
||||
private const string UnknownStatusKeyError = "Unknown file status key: '{0}'";
|
||||
|
||||
// NOTE: Has to stay in sync with GitFileStatus enum for FileStatusFromKey to function as intended
|
||||
static readonly string[] GitFileStatusKeys = {
|
||||
"??",
|
||||
"M",
|
||||
"A",
|
||||
"D",
|
||||
"R",
|
||||
"C"
|
||||
};
|
||||
|
||||
private static readonly string[] GitFileStatusKeys = { "??", "M", "A", "D", "R", "C" };
|
||||
|
||||
public static bool TryParse(string line, out GitStatusEntry entry)
|
||||
{
|
||||
Match match = Utility.StatusStartRegex.Match(line);
|
||||
string
|
||||
statusKey = match.Groups["status"].Value,
|
||||
path = match.Groups["path"].Value;
|
||||
var match = Utility.StatusStartRegex.Match(line);
|
||||
string statusKey = match.Groups["status"].Value, path = match.Groups["path"].Value;
|
||||
|
||||
if (!string.IsNullOrEmpty(statusKey) && !string.IsNullOrEmpty(path))
|
||||
{
|
||||
GitFileStatus status = FileStatusFromKey(statusKey);
|
||||
int renameIndex = line.IndexOf(Utility.StatusRenameDivider);
|
||||
var status = FileStatusFromKey(statusKey);
|
||||
var renameIndex = line.IndexOf(Utility.StatusRenameDivider);
|
||||
|
||||
if (renameIndex >= 0)
|
||||
{
|
||||
|
@ -86,29 +63,22 @@ namespace GitHub.Unity
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
static GitFileStatus FileStatusFromKey(string key)
|
||||
private static GitFileStatus FileStatusFromKey(string key)
|
||||
{
|
||||
for(int index = 0; index < GitFileStatusKeys.Length; ++index)
|
||||
for (var index = 0; index < GitFileStatusKeys.Length; ++index)
|
||||
{
|
||||
if(key.Equals(GitFileStatusKeys[index]))
|
||||
if (key.Equals(GitFileStatusKeys[index]))
|
||||
{
|
||||
return (GitFileStatus)index;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentException(string.Format(UnknownStatusKeyError, key));
|
||||
throw new ArgumentException(String.Format(UnknownStatusKeyError, key));
|
||||
}
|
||||
|
||||
|
||||
public readonly string
|
||||
Path,
|
||||
FullPath,
|
||||
ProjectPath,
|
||||
OriginalPath;
|
||||
public readonly string Path, FullPath, ProjectPath, OriginalPath;
|
||||
public readonly GitFileStatus Status;
|
||||
|
||||
|
||||
public GitStatusEntry(string path, GitFileStatus status, string originalPath = "")
|
||||
{
|
||||
Path = path;
|
||||
|
@ -118,65 +88,25 @@ namespace GitHub.Unity
|
|||
OriginalPath = originalPath;
|
||||
}
|
||||
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Path.GetHashCode();
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("'{0}': {1}", Path, Status);
|
||||
return String.Format("'{0}': {1}", Path, Status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class GitStatusTask : GitTask
|
||||
{
|
||||
const string BranchNamesSeparator = "...";
|
||||
private const string BranchNamesSeparator = "...";
|
||||
|
||||
private static Action<GitStatus> onStatusUpdate;
|
||||
private GitStatus status;
|
||||
|
||||
static Action<GitStatus> onStatusUpdate;
|
||||
|
||||
|
||||
public static void RegisterCallback(Action<GitStatus> callback)
|
||||
{
|
||||
onStatusUpdate += callback;
|
||||
}
|
||||
|
||||
|
||||
public static void UnregisterCallback(Action<GitStatus> callback)
|
||||
{
|
||||
onStatusUpdate -= callback;
|
||||
}
|
||||
|
||||
|
||||
public static void Schedule()
|
||||
{
|
||||
GitListUntrackedFilesTask.Schedule(task => Tasks.Add(new GitStatusTask(task.Entries)));
|
||||
}
|
||||
|
||||
|
||||
public override bool Blocking { get { return false; } }
|
||||
public virtual TaskQueueSetting Queued { get { return TaskQueueSetting.QueueSingle; } }
|
||||
public override bool Critical { get { return false; } }
|
||||
public override bool Cached { get { return false; } }
|
||||
public override string Label { get { return "git status"; } }
|
||||
|
||||
|
||||
protected override string ProcessArguments { get { return "status -b --porcelain"; } }
|
||||
protected override TextWriter OutputBuffer { get { return output; } }
|
||||
protected override TextWriter ErrorBuffer { get { return error; } }
|
||||
|
||||
|
||||
StringWriter
|
||||
output = new StringWriter(),
|
||||
error = new StringWriter();
|
||||
GitStatus status;
|
||||
|
||||
|
||||
GitStatusTask(IList<GitStatusEntry> existingEntries = null)
|
||||
private GitStatusTask(IList<GitStatusEntry> existingEntries = null)
|
||||
{
|
||||
status.Entries = new List<GitStatusEntry>();
|
||||
if (existingEntries != null)
|
||||
|
@ -185,30 +115,42 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
public static void RegisterCallback(Action<GitStatus> callback)
|
||||
{
|
||||
onStatusUpdate += callback;
|
||||
}
|
||||
|
||||
public static void UnregisterCallback(Action<GitStatus> callback)
|
||||
{
|
||||
onStatusUpdate -= callback;
|
||||
}
|
||||
|
||||
public static void Schedule()
|
||||
{
|
||||
GitListUntrackedFilesTask.Schedule(task => Tasks.Add(new GitStatusTask(task.Entries)));
|
||||
}
|
||||
|
||||
protected override void OnProcessOutputUpdate()
|
||||
{
|
||||
Utility.ParseLines(output.GetStringBuilder(), ParseOutputLine, Done);
|
||||
Utility.ParseLines(OutputBuffer.GetStringBuilder(), ParseOutputLine, Done);
|
||||
|
||||
if(Done)
|
||||
// If we are done, hand over the results to any listeners on the main thread
|
||||
if (Done)
|
||||
{
|
||||
Tasks.ScheduleMainThread(DeliverResult);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DeliverResult()
|
||||
private void DeliverResult()
|
||||
{
|
||||
if(onStatusUpdate != null)
|
||||
if (onStatusUpdate != null)
|
||||
{
|
||||
onStatusUpdate(status);
|
||||
}
|
||||
status.Clear();
|
||||
}
|
||||
|
||||
|
||||
void ParseOutputLine(string line)
|
||||
private void ParseOutputLine(string line)
|
||||
{
|
||||
GitStatusEntry entry;
|
||||
|
||||
|
@ -222,27 +164,26 @@ namespace GitHub.Unity
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
// Grab local and remote branch
|
||||
if (Utility.StatusBranchLineValidRegex.Match(line).Success)
|
||||
{
|
||||
int index = line.IndexOf(BranchNamesSeparator);
|
||||
if (index >= 0)
|
||||
var index = line.IndexOf(BranchNamesSeparator);
|
||||
|
||||
// Remote branch available
|
||||
if (index >= 0)
|
||||
{
|
||||
status.LocalBranch = line.Substring(2, index - 2);
|
||||
status.RemoteBranch = line.Substring(index + BranchNamesSeparator.Length);
|
||||
index = status.RemoteBranch.IndexOf('[');
|
||||
if (index > 0)
|
||||
|
||||
// Ahead and/or behind information available
|
||||
if (index > 0)
|
||||
{
|
||||
Match match = Utility.StatusAheadBehindRegex.Match(status.RemoteBranch.Substring(index - 1));
|
||||
var match = Utility.StatusAheadBehindRegex.Match(status.RemoteBranch.Substring(index - 1));
|
||||
|
||||
status.RemoteBranch = status.RemoteBranch.Substring(0, index).Trim();
|
||||
|
||||
string
|
||||
aheadString = match.Groups["ahead"].Value,
|
||||
behindString = match.Groups["behind"].Value;
|
||||
string aheadString = match.Groups["ahead"].Value, behindString = match.Groups["behind"].Value;
|
||||
|
||||
status.Ahead = string.IsNullOrEmpty(aheadString) ? 0 : Int32.Parse(aheadString);
|
||||
status.Behind = string.IsNullOrEmpty(behindString) ? 0 : Int32.Parse(behindString);
|
||||
|
@ -252,12 +193,42 @@ namespace GitHub.Unity
|
|||
status.RemoteBranch = status.RemoteBranch.Trim();
|
||||
}
|
||||
}
|
||||
else
|
||||
// No remote branch
|
||||
else
|
||||
{
|
||||
status.LocalBranch = line.Substring(2).Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Blocking
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public virtual TaskQueueSetting Queued
|
||||
{
|
||||
get { return TaskQueueSetting.QueueSingle; }
|
||||
}
|
||||
|
||||
public override bool Critical
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool Cached
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override string Label
|
||||
{
|
||||
get { return "git status"; }
|
||||
}
|
||||
|
||||
protected override string ProcessArguments
|
||||
{
|
||||
get { return "status -b --porcelain"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,62 +1,39 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
class GitSwitchBranchesTask : GitTask
|
||||
{
|
||||
const string SwitchConfirmedMessage = "Switched to branch '{0}'";
|
||||
private const string SwitchConfirmedMessage = "Switched to branch '{0}'";
|
||||
|
||||
private string branch;
|
||||
private Action onFailure;
|
||||
private Action onSuccess;
|
||||
|
||||
public static void Schedule(string branch, Action onSuccess, Action onFailure = null)
|
||||
{
|
||||
Tasks.Add(new GitSwitchBranchesTask(branch, onSuccess, onFailure));
|
||||
}
|
||||
|
||||
|
||||
string branch;
|
||||
Action
|
||||
onSuccess,
|
||||
onFailure;
|
||||
StringWriter
|
||||
output = new StringWriter(),
|
||||
error = new StringWriter();
|
||||
|
||||
|
||||
public override bool Blocking { get { return true; } }
|
||||
public override TaskQueueSetting Queued { get { return TaskQueueSetting.QueueSingle; } }
|
||||
public override bool Critical { get { return true; } }
|
||||
public override bool Cached { get { return false; } }
|
||||
public override string Label { get { return "git checkout"; } }
|
||||
|
||||
protected override string ProcessArguments { get { return string.Format("checkout {0}", branch); } }
|
||||
protected override TextWriter OutputBuffer { get { return output; } }
|
||||
protected override TextWriter ErrorBuffer { get { return error; } }
|
||||
|
||||
|
||||
GitSwitchBranchesTask(string branch, Action onSuccess, Action onFailure = null)
|
||||
private GitSwitchBranchesTask(string branch, Action onSuccess, Action onFailure = null)
|
||||
{
|
||||
this.branch = branch;
|
||||
this.onSuccess = onSuccess;
|
||||
this.onFailure = onFailure;
|
||||
}
|
||||
|
||||
public static void Schedule(string branch, Action onSuccess, Action onFailure = null)
|
||||
{
|
||||
Tasks.Add(new GitSwitchBranchesTask(branch, onSuccess, onFailure));
|
||||
}
|
||||
|
||||
protected override void OnProcessOutputUpdate()
|
||||
{
|
||||
if (Done)
|
||||
{
|
||||
// Handle failure / success
|
||||
StringBuilder buffer = error.GetStringBuilder();
|
||||
var buffer = ErrorBuffer.GetStringBuilder();
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
string message = buffer.ToString().Trim();
|
||||
var message = buffer.ToString().Trim();
|
||||
|
||||
if (!message.Equals(string.Format(SwitchConfirmedMessage, branch)))
|
||||
if (!message.Equals(String.Format(SwitchConfirmedMessage, branch)))
|
||||
{
|
||||
Tasks.ReportFailure(FailureSeverity.Critical, this, message);
|
||||
if (onFailure != null)
|
||||
|
@ -74,5 +51,35 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Blocking
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override TaskQueueSetting Queued
|
||||
{
|
||||
get { return TaskQueueSetting.QueueSingle; }
|
||||
}
|
||||
|
||||
public override bool Critical
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override bool Cached
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override string Label
|
||||
{
|
||||
get { return "git checkout"; }
|
||||
}
|
||||
|
||||
protected override string ProcessArguments
|
||||
{
|
||||
get { return String.Format("checkout {0}", branch); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,7 @@ namespace GitHub.Unity
|
|||
{
|
||||
class GitTask : ProcessTask
|
||||
{
|
||||
const string NoGitError = "Tried to run git task while git was not found.";
|
||||
|
||||
|
||||
protected override string ProcessName { get { return Utility.GitInstallPath; } }
|
||||
|
||||
private const string NoGitError = "Tried to run git task while git was not found.";
|
||||
|
||||
public override void Run()
|
||||
{
|
||||
|
@ -19,5 +15,10 @@ namespace GitHub.Unity
|
|||
|
||||
base.Run();
|
||||
}
|
||||
|
||||
protected override string ProcessName
|
||||
{
|
||||
get { return Utility.GitInstallPath; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,54 +1,35 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using UnityEditor;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
class ProcessTask : ITask
|
||||
class ProcessTask : ITask, IDisposable
|
||||
{
|
||||
const int ExitMonitorSleep = 10;
|
||||
|
||||
|
||||
[MenuItem("Assets/GitHub/Process Test")]
|
||||
static void Test()
|
||||
{
|
||||
EditorApplication.delayCall += () => Tasks.Add(new ProcessTask());
|
||||
}
|
||||
|
||||
|
||||
public virtual bool Blocking { get { return true; } }
|
||||
public virtual float Progress { get; protected set; }
|
||||
public virtual bool Done { get; protected set; }
|
||||
public virtual TaskQueueSetting Queued { get { return TaskQueueSetting.Queue; } }
|
||||
public virtual bool Critical { get { return true; } }
|
||||
public virtual bool Cached { get { return true; } }
|
||||
public virtual Action<ITask> OnBegin { get; set; }
|
||||
public virtual Action<ITask> OnEnd { get; set; }
|
||||
public virtual string Label { get { return "Process task"; } }
|
||||
|
||||
|
||||
protected virtual string ProcessName { get { return "sleep"; } }
|
||||
protected virtual string ProcessArguments { get { return "20"; } }
|
||||
protected virtual CachedTask CachedTaskType { get { return CachedTask.ProcessTask; } }
|
||||
protected virtual TextWriter OutputBuffer { get { return null; } }
|
||||
protected virtual TextWriter ErrorBuffer { get { return null; } }
|
||||
|
||||
|
||||
Process process;
|
||||
private const int ExitMonitorSleep = 10;
|
||||
|
||||
private readonly StringWriter error = new StringWriter();
|
||||
private readonly StringWriter output = new StringWriter();
|
||||
private Process process;
|
||||
|
||||
protected ProcessTask()
|
||||
{}
|
||||
|
||||
[MenuItem("Assets/GitHub/Process Test")]
|
||||
public static void Test()
|
||||
{
|
||||
EditorApplication.delayCall += () => Tasks.Add(new ProcessTask());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to reattach to the process. Assume that we're done if that fails.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static ProcessTask Parse(IDictionary<string, object> data)
|
||||
// Try to reattach to the process. Assume that we're done if that fails.
|
||||
{
|
||||
Process resumedProcess;
|
||||
|
||||
|
@ -57,20 +38,18 @@ namespace GitHub.Unity
|
|||
resumedProcess = Process.GetProcessById((int)(Int64)data[Tasks.ProcessKey]);
|
||||
resumedProcess.StartInfo.RedirectStandardOutput = resumedProcess.StartInfo.RedirectStandardError = true;
|
||||
}
|
||||
catch(Exception)
|
||||
catch (Exception)
|
||||
{
|
||||
resumedProcess = null;
|
||||
}
|
||||
|
||||
return new ProcessTask()
|
||||
{
|
||||
return new ProcessTask() {
|
||||
process = resumedProcess,
|
||||
Done = resumedProcess == null,
|
||||
Progress = resumedProcess == null ? 1f : 0f
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public virtual void Run()
|
||||
{
|
||||
Debug.LogFormat("{0} {1}", Label, process == null ? "start" : "reconnect");
|
||||
|
@ -78,66 +57,53 @@ namespace GitHub.Unity
|
|||
Done = false;
|
||||
Progress = 0.0f;
|
||||
|
||||
if(OnBegin != null)
|
||||
if (OnBegin != null)
|
||||
{
|
||||
OnBegin(this);
|
||||
}
|
||||
|
||||
if(process == null)
|
||||
// Only start the process if we haven't already reconnected to an existing instance
|
||||
if (process == null)
|
||||
{
|
||||
process = Process.Start(new ProcessStartInfo(ProcessName, ProcessArguments)
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardOutput = true,
|
||||
WorkingDirectory = Utility.GitRoot
|
||||
});
|
||||
process =
|
||||
Process.Start(new ProcessStartInfo(ProcessName, ProcessArguments) {
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardOutput = true,
|
||||
WorkingDirectory = Utility.GitRoot
|
||||
});
|
||||
}
|
||||
|
||||
TextWriter
|
||||
output = OutputBuffer,
|
||||
error = ErrorBuffer;
|
||||
|
||||
// NOTE: WaitForExit is too low level here. Won't be properly interrupted by thread abort.
|
||||
do
|
||||
{
|
||||
// Wait a bit
|
||||
Thread.Sleep(ExitMonitorSleep);
|
||||
|
||||
// Read all available process output //
|
||||
// Read all available process output
|
||||
var updated = false;
|
||||
|
||||
bool updated = false;
|
||||
|
||||
while(!process.StandardOutput.EndOfStream)
|
||||
while (!process.StandardOutput.EndOfStream)
|
||||
{
|
||||
char read = (char)process.StandardOutput.Read();
|
||||
if(output != null)
|
||||
{
|
||||
output.Write(read);
|
||||
updated = true;
|
||||
}
|
||||
var read = (char)process.StandardOutput.Read();
|
||||
OutputBuffer.Write(read);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
while(!process.StandardError.EndOfStream)
|
||||
while (!process.StandardError.EndOfStream)
|
||||
{
|
||||
char read = (char)process.StandardError.Read();
|
||||
if(error != null)
|
||||
{
|
||||
error.Write(read);
|
||||
updated = true;
|
||||
}
|
||||
var read = (char)process.StandardError.Read();
|
||||
ErrorBuffer.Write(read);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
// Notify if anything was read //
|
||||
|
||||
if(updated)
|
||||
// Notify if anything was read
|
||||
if (updated)
|
||||
{
|
||||
OnProcessOutputUpdate();
|
||||
}
|
||||
}
|
||||
while(!process.HasExited);
|
||||
} while (!process.HasExited);
|
||||
|
||||
Progress = 1.0f;
|
||||
Done = true;
|
||||
|
@ -146,13 +112,12 @@ namespace GitHub.Unity
|
|||
|
||||
Debug.LogFormat("{0} end", Label);
|
||||
|
||||
if(OnEnd != null)
|
||||
if (OnEnd != null)
|
||||
{
|
||||
OnEnd(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Abort()
|
||||
{
|
||||
Debug.LogFormat("Aborting {0}", Label);
|
||||
|
@ -161,18 +126,17 @@ namespace GitHub.Unity
|
|||
{
|
||||
process.Kill();
|
||||
}
|
||||
catch(Exception)
|
||||
catch (Exception)
|
||||
{}
|
||||
|
||||
Done = true;
|
||||
|
||||
if(OnEnd != null)
|
||||
if (OnEnd != null)
|
||||
{
|
||||
OnEnd(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
Debug.LogFormat("Disconnect {0}", Label);
|
||||
|
@ -180,27 +144,95 @@ namespace GitHub.Unity
|
|||
process = null;
|
||||
}
|
||||
|
||||
|
||||
public void Reconnect()
|
||||
{}
|
||||
|
||||
|
||||
public void WriteCache(TextWriter cache)
|
||||
{
|
||||
Debug.LogFormat("Writing cache for {0}", Label);
|
||||
|
||||
cache.Write(
|
||||
@"{{
|
||||
""{0}"": ""{1}"",
|
||||
""{2}"": {3}
|
||||
}}",
|
||||
Tasks.TypeKey, CachedTaskType,
|
||||
Tasks.ProcessKey, process == null ? -1 : process.Id
|
||||
);
|
||||
cache.WriteLine("{");
|
||||
cache.WriteLine(String.Format("\"{0}\": \"{1}\",", Tasks.TypeKey, CachedTaskType));
|
||||
cache.WriteLine(String.Format("\"{0}\": \"{1}\"", Tasks.ProcessKey, process == null ? -1 : process.Id));
|
||||
cache.WriteLine("}");
|
||||
}
|
||||
|
||||
|
||||
protected virtual void OnProcessOutputUpdate()
|
||||
{}
|
||||
|
||||
bool disposed = false;
|
||||
public virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
disposed = true;
|
||||
ErrorBuffer.Dispose();
|
||||
OutputBuffer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public virtual bool Blocking
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public virtual float Progress { get; protected set; }
|
||||
public virtual bool Done { get; protected set; }
|
||||
|
||||
public virtual TaskQueueSetting Queued
|
||||
{
|
||||
get { return TaskQueueSetting.Queue; }
|
||||
}
|
||||
|
||||
public virtual bool Critical
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public virtual bool Cached
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public virtual Action<ITask> OnBegin { get; set; }
|
||||
public virtual Action<ITask> OnEnd { get; set; }
|
||||
|
||||
public virtual string Label
|
||||
{
|
||||
get { return "Process task"; }
|
||||
}
|
||||
|
||||
protected virtual string ProcessName
|
||||
{
|
||||
get { return "sleep"; }
|
||||
}
|
||||
|
||||
protected virtual string ProcessArguments
|
||||
{
|
||||
get { return "20"; }
|
||||
}
|
||||
|
||||
protected virtual CachedTask CachedTaskType
|
||||
{
|
||||
get { return CachedTask.ProcessTask; }
|
||||
}
|
||||
|
||||
protected virtual StringWriter OutputBuffer
|
||||
{
|
||||
get { return output; }
|
||||
}
|
||||
|
||||
protected virtual StringWriter ErrorBuffer
|
||||
{
|
||||
get { return error; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
[Serializable]
|
||||
public class TaskException : Exception
|
||||
{
|
||||
public TaskException()
|
||||
{
|
||||
}
|
||||
|
||||
public TaskException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public TaskException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected TaskException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,12 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEngine.Events;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
/*
|
||||
|
||||
|
@ -22,7 +20,6 @@ using System.Linq;
|
|||
|
||||
*/
|
||||
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
enum TaskQueueSetting
|
||||
|
@ -32,9 +29,13 @@ namespace GitHub.Unity
|
|||
QueueSingle
|
||||
}
|
||||
|
||||
|
||||
interface ITask
|
||||
{
|
||||
void Run();
|
||||
void Abort();
|
||||
void Disconnect();
|
||||
void Reconnect();
|
||||
void WriteCache(TextWriter cache);
|
||||
bool Blocking { get; }
|
||||
float Progress { get; }
|
||||
bool Done { get; }
|
||||
|
@ -44,107 +45,76 @@ namespace GitHub.Unity
|
|||
Action<ITask> OnBegin { set; }
|
||||
Action<ITask> OnEnd { set; }
|
||||
string Label { get; }
|
||||
void Run();
|
||||
void Abort();
|
||||
void Disconnect();
|
||||
void Reconnect();
|
||||
void WriteCache(TextWriter cache);
|
||||
};
|
||||
|
||||
|
||||
enum CachedTask
|
||||
{
|
||||
TestTask,
|
||||
ProcessTask
|
||||
};
|
||||
|
||||
|
||||
enum FailureSeverity
|
||||
{
|
||||
Moderate,
|
||||
Critical
|
||||
};
|
||||
|
||||
|
||||
class Tasks
|
||||
{
|
||||
enum WaitMode
|
||||
internal const string TypeKey = "type", ProcessKey = "process";
|
||||
|
||||
private const int NoTasksSleep = 100;
|
||||
private const int BlockingTaskWaitSleep = 10;
|
||||
private const int FailureDelayDefault = 1;
|
||||
private const int FailureDelayLong = 5000;
|
||||
private const string CacheFileName = "GitHubCache";
|
||||
private const string QuitActionFieldName = "editorApplicationQuit";
|
||||
private const string TaskThreadExceptionRestartError = "GitHub task thread restarting after encountering an exception: {0}";
|
||||
private const string TaskCacheWriteExceptionError = "GitHub: Exception when writing task cache: {0}";
|
||||
private const string TaskCacheParseError = "GitHub: Failed to parse task cache";
|
||||
private const string TaskParseUnhandledTypeError = "GitHub: Trying to parse unhandled cached task: {0}";
|
||||
private const string TaskFailureTitle = "GitHub";
|
||||
private const string TaskFailureMessage = "{0} failed:\n{1}";
|
||||
private const string TaskFailureOK = "OK";
|
||||
private const string TaskProgressTitle = "GitHub";
|
||||
private const string TaskBlockingTitle = "Critical GitHub task";
|
||||
private const string TaskBlockingDescription = "A critical GitHub task ({0}) has yet to complete. What would you like to do?";
|
||||
private const string TaskBlockingComplete = "Complete";
|
||||
private const string TaskBlockingInterrupt = "Interrupt";
|
||||
private const BindingFlags kQuitActionBindingFlags = BindingFlags.NonPublic | BindingFlags.Static;
|
||||
|
||||
private static FieldInfo quitActionField;
|
||||
private static ProgressBarDisplayMethod displayBackgroundProgressBar;
|
||||
private static Action clearBackgroundProgressBar;
|
||||
private ITask activeTask;
|
||||
private Exception lastException;
|
||||
|
||||
private bool running = false;
|
||||
private Queue<ITask> tasks;
|
||||
private object tasksLock = new object();
|
||||
private Thread thread;
|
||||
|
||||
private Tasks()
|
||||
{
|
||||
Background,
|
||||
Modal,
|
||||
Blocking
|
||||
};
|
||||
|
||||
|
||||
delegate void ProgressBarDisplayMethod(string text, float progress);
|
||||
|
||||
|
||||
internal const string
|
||||
TypeKey = "type",
|
||||
ProcessKey = "process";
|
||||
|
||||
|
||||
const int
|
||||
NoTasksSleep = 100,
|
||||
BlockingTaskWaitSleep = 10,
|
||||
FailureDelayDefault = 1,
|
||||
FailureDelayLong = 5000;
|
||||
const string
|
||||
CacheFileName = "GitHubCache",
|
||||
QuitActionFieldName = "editorApplicationQuit",
|
||||
TaskThreadExceptionRestartError = "GitHub task thread restarting after encountering an exception: {0}",
|
||||
TaskCacheWriteExceptionError = "GitHub: Exception when writing task cache: {0}",
|
||||
TaskCacheParseError = "GitHub: Failed to parse task cache",
|
||||
TaskParseUnhandledTypeError = "GitHub: Trying to parse unhandled cached task: {0}",
|
||||
TaskFailureTitle = "GitHub",
|
||||
TaskFailureMessage = "{0} failed:\n{1}",
|
||||
TaskFailureOK = "OK",
|
||||
TaskProgressTitle = "GitHub",
|
||||
TaskBlockingTitle = "Critical GitHub task",
|
||||
TaskBlockingDescription = "A critical GitHub task ({0}) has yet to complete. What would you like to do?",
|
||||
TaskBlockingComplete = "Complete",
|
||||
TaskBlockingInterrupt = "Interrupt";
|
||||
const BindingFlags kQuitActionBindingFlags = BindingFlags.NonPublic | BindingFlags.Static;
|
||||
|
||||
|
||||
static FieldInfo quitActionField;
|
||||
static ProgressBarDisplayMethod displayBackgroundProgressBar;
|
||||
static Action clearBackgroundProgressBar;
|
||||
|
||||
|
||||
static Tasks Instance { get; set; }
|
||||
static string CacheFilePath { get; set; }
|
||||
|
||||
|
||||
static void SecureQuitActionField()
|
||||
{
|
||||
if(quitActionField == null)
|
||||
{
|
||||
quitActionField = typeof(EditorApplication).GetField(QuitActionFieldName, kQuitActionBindingFlags);
|
||||
|
||||
if(quitActionField == null)
|
||||
editorApplicationQuit = (UnityAction)Delegate.Combine(editorApplicationQuit, new UnityAction(OnQuit));
|
||||
CacheFilePath = Path.Combine(Application.dataPath, Path.Combine("..", Path.Combine("Temp", CacheFileName)));
|
||||
EditorApplication.playmodeStateChanged += () => {
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode && !EditorApplication.isPlaying)
|
||||
{
|
||||
throw new NullReferenceException("Unable to reflect EditorApplication." + QuitActionFieldName);
|
||||
OnPlaymodeEnter();
|
||||
}
|
||||
};
|
||||
|
||||
tasks = new Queue<ITask>();
|
||||
if (File.Exists(CacheFilePath))
|
||||
{
|
||||
ReadCache();
|
||||
File.Delete(CacheFilePath);
|
||||
|
||||
OnSessionRestarted();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static UnityAction editorApplicationQuit
|
||||
{
|
||||
get
|
||||
{
|
||||
SecureQuitActionField();
|
||||
return (UnityAction)quitActionField.GetValue(null);
|
||||
}
|
||||
set
|
||||
{
|
||||
SecureQuitActionField();
|
||||
quitActionField.SetValue(null, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// "Everything is broken - let's rebuild from the ashes (read: cache)"
|
||||
public static void Initialize()
|
||||
{
|
||||
|
@ -157,46 +127,12 @@ namespace GitHub.Unity
|
|||
Instance.thread.Start();
|
||||
}
|
||||
|
||||
|
||||
bool running = false;
|
||||
Thread thread;
|
||||
ITask activeTask;
|
||||
Queue<ITask> tasks;
|
||||
object tasksLock = new object();
|
||||
Exception lastException;
|
||||
|
||||
|
||||
Tasks()
|
||||
{
|
||||
editorApplicationQuit = (UnityAction)Delegate.Combine(editorApplicationQuit, new UnityAction(OnQuit));
|
||||
CacheFilePath = Path.Combine(Application.dataPath, Path.Combine("..", Path.Combine("Temp", CacheFileName)));
|
||||
EditorApplication.playmodeStateChanged += () =>
|
||||
{
|
||||
if(EditorApplication.isPlayingOrWillChangePlaymode && !EditorApplication.isPlaying)
|
||||
{
|
||||
OnPlaymodeEnter();
|
||||
}
|
||||
};
|
||||
|
||||
tasks = new Queue<ITask>();
|
||||
if(File.Exists(CacheFilePath))
|
||||
{
|
||||
ReadCache();
|
||||
File.Delete(CacheFilePath);
|
||||
|
||||
OnSessionRestarted();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void Add(ITask task)
|
||||
{
|
||||
lock(Instance.tasksLock)
|
||||
{
|
||||
if(
|
||||
(task.Queued == TaskQueueSetting.NoQueue && Instance.tasks.Count > 0) ||
|
||||
(task.Queued == TaskQueueSetting.QueueSingle && Instance.tasks.Any(t => t.GetType() == task.GetType()))
|
||||
)
|
||||
if ((task.Queued == TaskQueueSetting.NoQueue && Instance.tasks.Count > 0) ||
|
||||
(task.Queued == TaskQueueSetting.QueueSingle && Instance.tasks.Any(t => t.GetType() == task.GetType())))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -207,10 +143,63 @@ namespace GitHub.Unity
|
|||
Instance.WriteCache();
|
||||
}
|
||||
|
||||
|
||||
void Start()
|
||||
public static void ScheduleMainThread(Action action)
|
||||
{
|
||||
while(true)
|
||||
EditorApplication.delayCall += () => action();
|
||||
}
|
||||
|
||||
public static void ReportFailure(FailureSeverity severity, ITask task, string error)
|
||||
{
|
||||
if (severity == FailureSeverity.Moderate)
|
||||
{
|
||||
Debug.LogErrorFormat(TaskFailureMessage, task.Label, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScheduleMainThread(
|
||||
() => EditorUtility.DisplayDialog(TaskFailureTitle, String.Format(TaskFailureMessage, task.Label, error), TaskFailureOK));
|
||||
}
|
||||
}
|
||||
|
||||
private static void SecureQuitActionField()
|
||||
{
|
||||
if (quitActionField == null)
|
||||
{
|
||||
quitActionField = typeof(EditorApplication).GetField(QuitActionFieldName, kQuitActionBindingFlags);
|
||||
|
||||
if (quitActionField == null)
|
||||
{
|
||||
throw new TaskException("Unable to reflect EditorApplication." + QuitActionFieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DisplayBackgroundProgressBar(string description, float progress)
|
||||
{
|
||||
if (displayBackgroundProgressBar == null)
|
||||
{
|
||||
var type = typeof(EditorWindow).Assembly.GetType("UnityEditor.AsyncProgressBar");
|
||||
displayBackgroundProgressBar =
|
||||
(ProgressBarDisplayMethod)
|
||||
Delegate.CreateDelegate(typeof(ProgressBarDisplayMethod),
|
||||
type.GetMethod("Display", new Type[] { typeof(string), typeof(float) }));
|
||||
}
|
||||
displayBackgroundProgressBar(description, progress);
|
||||
}
|
||||
|
||||
private static void ClearBackgroundProgressBar()
|
||||
{
|
||||
if (clearBackgroundProgressBar == null)
|
||||
{
|
||||
var type = typeof(EditorWindow).Assembly.GetType("UnityEditor.AsyncProgressBar");
|
||||
clearBackgroundProgressBar = (Action)Delegate.CreateDelegate(typeof(Action), type.GetMethod("Clear", new Type[] { }));
|
||||
}
|
||||
clearBackgroundProgressBar();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -218,15 +207,15 @@ namespace GitHub.Unity
|
|||
|
||||
break;
|
||||
}
|
||||
catch(ThreadAbortException)
|
||||
// Aborted by domain unload or explicitly via the editor quit handler. Button down the hatches.
|
||||
// Aborted by domain unload or explicitly via the editor quit handler. Button down the hatches.
|
||||
catch (ThreadAbortException)
|
||||
{
|
||||
running = false;
|
||||
|
||||
// Disconnect or abort the active task
|
||||
if(activeTask != null && !activeTask.Done)
|
||||
if (activeTask != null && !activeTask.Done)
|
||||
{
|
||||
if(activeTask.Cached)
|
||||
if (activeTask.Cached)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -252,14 +241,14 @@ namespace GitHub.Unity
|
|||
|
||||
break;
|
||||
}
|
||||
catch(Exception e)
|
||||
// Something broke internally - reboot
|
||||
// Something broke internally - reboot
|
||||
catch (Exception e)
|
||||
{
|
||||
running = false;
|
||||
bool repeat = lastException != null && e.TargetSite.Equals(lastException.TargetSite);
|
||||
var repeat = lastException != null && e.TargetSite.Equals(lastException.TargetSite);
|
||||
lastException = e;
|
||||
|
||||
if(!repeat)
|
||||
if (!repeat)
|
||||
{
|
||||
Debug.LogErrorFormat(TaskThreadExceptionRestartError, e);
|
||||
Thread.Sleep(FailureDelayDefault);
|
||||
|
@ -272,60 +261,56 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void OnPlaymodeEnter()
|
||||
// About to enter playmode
|
||||
private void OnPlaymodeEnter()
|
||||
{
|
||||
if(activeTask != null)
|
||||
if (activeTask != null)
|
||||
{
|
||||
ClearBackgroundProgressBar();
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OnSessionRestarted()
|
||||
// A recompile or playmode enter/exit cause the script environment to reload while we had tasks at hand
|
||||
private void OnSessionRestarted()
|
||||
{
|
||||
ClearBackgroundProgressBar();
|
||||
EditorUtility.ClearProgressBar();
|
||||
if(activeTask != null)
|
||||
if (activeTask != null)
|
||||
{
|
||||
activeTask.Reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OnQuit()
|
||||
private void OnQuit()
|
||||
{
|
||||
// Stop the queue
|
||||
running = false;
|
||||
|
||||
if(activeTask != null && activeTask.Critical)
|
||||
if (activeTask != null && activeTask.Critical)
|
||||
{
|
||||
WaitForTask(activeTask, WaitMode.Blocking);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RunInternal()
|
||||
private void RunInternal()
|
||||
{
|
||||
running = true;
|
||||
|
||||
while(running)
|
||||
while (running)
|
||||
{
|
||||
// Clear any completed task
|
||||
if(activeTask != null && activeTask.Done)
|
||||
if (activeTask != null && activeTask.Done)
|
||||
{
|
||||
activeTask = null;
|
||||
}
|
||||
|
||||
// Grab a new task
|
||||
if(activeTask == null)
|
||||
if (activeTask == null)
|
||||
{
|
||||
lock(tasksLock)
|
||||
{
|
||||
if(tasks.Count > 0)
|
||||
if (tasks.Count > 0)
|
||||
{
|
||||
activeTask = tasks.Dequeue();
|
||||
activeTask.OnBegin = task => ScheduleMainThread(WriteCache);
|
||||
|
@ -333,12 +318,11 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
if(activeTask != null)
|
||||
// Run and monitor active task
|
||||
if (activeTask != null)
|
||||
{
|
||||
ScheduleMainThread(() =>
|
||||
{
|
||||
if(activeTask != null)
|
||||
ScheduleMainThread(() => {
|
||||
if (activeTask != null)
|
||||
{
|
||||
WaitForTask(activeTask, activeTask.Blocking ? WaitMode.Modal : WaitMode.Background);
|
||||
}
|
||||
|
@ -357,16 +341,15 @@ namespace GitHub.Unity
|
|||
thread.Abort();
|
||||
}
|
||||
|
||||
|
||||
void WriteCache()
|
||||
private void WriteCache()
|
||||
{
|
||||
try
|
||||
{
|
||||
StreamWriter cache = File.CreateText(CacheFilePath);
|
||||
var cache = File.CreateText(CacheFilePath);
|
||||
cache.Write("[");
|
||||
|
||||
// Cache the active task
|
||||
if(activeTask != null && !activeTask.Done && activeTask.Cached)
|
||||
if (activeTask != null && !activeTask.Done && activeTask.Cached)
|
||||
{
|
||||
activeTask.WriteCache(cache);
|
||||
}
|
||||
|
@ -378,9 +361,9 @@ namespace GitHub.Unity
|
|||
// Cache the queue
|
||||
lock(tasksLock)
|
||||
{
|
||||
foreach(ITask task in tasks)
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
if(!task.Cached)
|
||||
if (!task.Cached)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
@ -393,38 +376,37 @@ namespace GitHub.Unity
|
|||
cache.Write("]");
|
||||
cache.Close();
|
||||
}
|
||||
catch(Exception e)
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogErrorFormat(TaskCacheWriteExceptionError, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool ReadCache()
|
||||
private bool ReadCache()
|
||||
{
|
||||
string text = File.ReadAllText(CacheFilePath);
|
||||
var text = File.ReadAllText(CacheFilePath);
|
||||
|
||||
object parseResult;
|
||||
IList<object> cache;
|
||||
|
||||
// Parse root list with at least one item (active task) or fail
|
||||
if(!SimpleJson.TryDeserializeObject(text, out parseResult) || (cache = parseResult as IList<object>) == null || cache.Count < 1)
|
||||
if (!SimpleJson.TryDeserializeObject(text, out parseResult) || (cache = parseResult as IList<object>) == null || cache.Count < 1)
|
||||
{
|
||||
Debug.LogError(TaskCacheParseError);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse active task
|
||||
IDictionary<string, object> taskData = cache[0] as IDictionary<string, object>;
|
||||
ITask cachedActiveTask = (taskData != null) ? ParseTask(taskData) : null;
|
||||
var taskData = cache[0] as IDictionary<string, object>;
|
||||
var cachedActiveTask = taskData != null ? ParseTask(taskData) : null;
|
||||
|
||||
// Parse tasks list or fail
|
||||
Queue<ITask> cachedTasks = new Queue<ITask>(cache.Count - 1);
|
||||
for(int index = 1; index < cache.Count; ++index)
|
||||
var cachedTasks = new Queue<ITask>(cache.Count - 1);
|
||||
for (var index = 1; index < cache.Count; ++index)
|
||||
{
|
||||
taskData = cache[index] as IDictionary<string, object>;
|
||||
|
||||
if(taskData == null)
|
||||
if (taskData == null)
|
||||
{
|
||||
Debug.LogError(TaskCacheParseError);
|
||||
return false;
|
||||
|
@ -440,8 +422,7 @@ namespace GitHub.Unity
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
ITask ParseTask(IDictionary<string, object> data)
|
||||
private ITask ParseTask(IDictionary<string, object> data)
|
||||
{
|
||||
CachedTask type;
|
||||
|
||||
|
@ -449,81 +430,77 @@ namespace GitHub.Unity
|
|||
{
|
||||
type = (CachedTask)Enum.Parse(typeof(CachedTask), (string)data[TypeKey]);
|
||||
}
|
||||
catch(Exception)
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
switch(type)
|
||||
switch (type)
|
||||
{
|
||||
case CachedTask.TestTask:
|
||||
return TestTask.Parse(data);
|
||||
return TestTask.Parse(data);
|
||||
case CachedTask.ProcessTask:
|
||||
return ProcessTask.Parse(data);
|
||||
return ProcessTask.Parse(data);
|
||||
default:
|
||||
Debug.LogErrorFormat(TaskParseUnhandledTypeError, type);
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch(Exception)
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WaitForTask(ITask task, WaitMode mode = WaitMode.Background)
|
||||
// Update progress bars to match progress of given task
|
||||
/// <summary>
|
||||
/// Update progress bars to match progress of given task
|
||||
/// </summary>
|
||||
private void WaitForTask(ITask task, WaitMode mode = WaitMode.Background)
|
||||
{
|
||||
if(activeTask != task)
|
||||
if (activeTask != task)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(mode == WaitMode.Background)
|
||||
// Unintrusive background process
|
||||
if (mode == WaitMode.Background)
|
||||
{
|
||||
task.OnEnd = OnWaitingBackgroundTaskEnd;
|
||||
|
||||
DisplayBackgroundProgressBar(task.Label, task.Progress);
|
||||
|
||||
if(!task.Done)
|
||||
if (!task.Done)
|
||||
{
|
||||
ScheduleMainThread(() => WaitForTask(task, mode));
|
||||
}
|
||||
}
|
||||
else if(mode == WaitMode.Modal)
|
||||
// Obstruct editor interface, while offering cancel button
|
||||
else if (mode == WaitMode.Modal)
|
||||
{
|
||||
task.OnEnd = OnWaitingModalTaskEnd;
|
||||
|
||||
if(!EditorUtility.DisplayCancelableProgressBar(TaskProgressTitle, task.Label, task.Progress) && !task.Done)
|
||||
if (!EditorUtility.DisplayCancelableProgressBar(TaskProgressTitle, task.Label, task.Progress) && !task.Done)
|
||||
{
|
||||
ScheduleMainThread(() => WaitForTask(task, mode));
|
||||
}
|
||||
else if(!task.Done)
|
||||
else if (!task.Done)
|
||||
{
|
||||
task.Abort();
|
||||
}
|
||||
}
|
||||
else
|
||||
// Offer to interrupt task via dialog box, else block main thread until completion
|
||||
else
|
||||
{
|
||||
if(EditorUtility.DisplayDialog(
|
||||
TaskBlockingTitle,
|
||||
string.Format(TaskBlockingDescription, task.Label),
|
||||
TaskBlockingComplete,
|
||||
TaskBlockingInterrupt
|
||||
))
|
||||
if (EditorUtility.DisplayDialog(TaskBlockingTitle, String.Format(TaskBlockingDescription, task.Label), TaskBlockingComplete,
|
||||
TaskBlockingInterrupt))
|
||||
{
|
||||
do
|
||||
{
|
||||
EditorUtility.DisplayProgressBar(TaskProgressTitle, task.Label, task.Progress);
|
||||
Thread.Sleep(BlockingTaskWaitSleep);
|
||||
}
|
||||
while(!task.Done);
|
||||
} while (!task.Done);
|
||||
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
|
@ -534,62 +511,40 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
static void DisplayBackgroundProgressBar(string description, float progress)
|
||||
{
|
||||
if(displayBackgroundProgressBar == null)
|
||||
{
|
||||
Type type = typeof(EditorWindow).Assembly.GetType("UnityEditor.AsyncProgressBar");
|
||||
displayBackgroundProgressBar = (ProgressBarDisplayMethod)Delegate.CreateDelegate(
|
||||
typeof(ProgressBarDisplayMethod),
|
||||
type.GetMethod("Display", new Type[]{ typeof(string), typeof(float) })
|
||||
);
|
||||
}
|
||||
|
||||
displayBackgroundProgressBar(description, progress);
|
||||
}
|
||||
|
||||
|
||||
static void ClearBackgroundProgressBar()
|
||||
{
|
||||
if(clearBackgroundProgressBar == null)
|
||||
{
|
||||
Type type = typeof(EditorWindow).Assembly.GetType("UnityEditor.AsyncProgressBar");
|
||||
clearBackgroundProgressBar = (Action)Delegate.CreateDelegate(typeof(Action), type.GetMethod("Clear", new Type[]{}));
|
||||
}
|
||||
|
||||
clearBackgroundProgressBar();
|
||||
}
|
||||
|
||||
|
||||
void OnWaitingBackgroundTaskEnd(ITask task)
|
||||
private void OnWaitingBackgroundTaskEnd(ITask task)
|
||||
{
|
||||
ScheduleMainThread(() => ClearBackgroundProgressBar());
|
||||
}
|
||||
|
||||
|
||||
void OnWaitingModalTaskEnd(ITask task)
|
||||
private void OnWaitingModalTaskEnd(ITask task)
|
||||
{
|
||||
ScheduleMainThread(() => EditorUtility.ClearProgressBar());
|
||||
}
|
||||
|
||||
private static Tasks Instance { get; set; }
|
||||
private static string CacheFilePath { get; set; }
|
||||
|
||||
public static void ScheduleMainThread(Action action)
|
||||
private static UnityAction editorApplicationQuit
|
||||
{
|
||||
EditorApplication.delayCall += () => action();
|
||||
}
|
||||
|
||||
|
||||
public static void ReportFailure(FailureSeverity severity, ITask task, string error)
|
||||
{
|
||||
if (severity == FailureSeverity.Moderate)
|
||||
get
|
||||
{
|
||||
Debug.LogErrorFormat(TaskFailureMessage, task.Label, error);
|
||||
SecureQuitActionField();
|
||||
return (UnityAction)quitActionField.GetValue(null);
|
||||
}
|
||||
else
|
||||
set
|
||||
{
|
||||
ScheduleMainThread(() => EditorUtility.DisplayDialog(TaskFailureTitle, string.Format(TaskFailureMessage, task.Label, error), TaskFailureOK));
|
||||
SecureQuitActionField();
|
||||
quitActionField.SetValue(null, value);
|
||||
}
|
||||
}
|
||||
|
||||
private enum WaitMode
|
||||
{
|
||||
Background,
|
||||
Modal,
|
||||
Blocking
|
||||
};
|
||||
|
||||
private delegate void ProgressBarDisplayMethod(string text, float progress);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +1,39 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Threading;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
class TestTask : ITask
|
||||
{
|
||||
[MenuItem("Assets/GitHub/Test Blocking Critical")]
|
||||
static void TestA()
|
||||
{
|
||||
Test(new TestTask(true));
|
||||
}
|
||||
private bool reconnecting = false;
|
||||
|
||||
|
||||
[MenuItem("Assets/GitHub/Test Non-blocking Critical")]
|
||||
static void TestB()
|
||||
{
|
||||
Test(new TestTask(false));
|
||||
}
|
||||
|
||||
|
||||
static void Test(TestTask task)
|
||||
{
|
||||
EditorApplication.delayCall += () => Tasks.Add(task);
|
||||
}
|
||||
|
||||
|
||||
public static TestTask Parse(IDictionary<string, object> data)
|
||||
{
|
||||
return new TestTask(false)
|
||||
{
|
||||
reconnecting = true,
|
||||
Done = false
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
TestTask(bool shouldBlock)
|
||||
private TestTask(bool shouldBlock)
|
||||
{
|
||||
Blocking = shouldBlock;
|
||||
Done = false;
|
||||
Progress = 0.0f;
|
||||
}
|
||||
|
||||
public static TestTask Parse(IDictionary<string, object> data)
|
||||
{
|
||||
return new TestTask(false) { reconnecting = true, Done = false };
|
||||
}
|
||||
|
||||
public bool Blocking { get; protected set; }
|
||||
public float Progress { get; protected set; }
|
||||
public bool Done { get; protected set; }
|
||||
public TaskQueueSetting Queued { get { return TaskQueueSetting.Queue; } }
|
||||
public bool Critical { get { return true; } }
|
||||
public bool Cached { get { return true; } }
|
||||
public Action<ITask> OnBegin { get; set; }
|
||||
public Action<ITask> OnEnd { get; set; }
|
||||
public string Label { get { return "Test task"; } }
|
||||
|
||||
|
||||
bool reconnecting = false;
|
||||
[MenuItem("Assets/GitHub/Test Blocking Critical")]
|
||||
public static void TestA()
|
||||
{
|
||||
Test(new TestTask(true));
|
||||
}
|
||||
|
||||
[MenuItem("Assets/GitHub/Test Non-blocking Critical")]
|
||||
public static void TestB()
|
||||
{
|
||||
Test(new TestTask(false));
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
|
@ -69,19 +42,17 @@ namespace GitHub.Unity
|
|||
Done = false;
|
||||
Progress = 0.0f;
|
||||
|
||||
if(OnBegin != null)
|
||||
if (OnBegin != null)
|
||||
{
|
||||
OnBegin(this);
|
||||
}
|
||||
|
||||
const int
|
||||
kSteps = 20,
|
||||
kStepSleep = 1000;
|
||||
const int kSteps = 20, kStepSleep = 1000;
|
||||
|
||||
for(int step = 0; !Done && step < kSteps; ++step)
|
||||
for (var step = 0; !Done && step < kSteps; ++step)
|
||||
{
|
||||
Progress = step / (float)kSteps;
|
||||
Thread.Sleep (kStepSleep);
|
||||
Thread.Sleep(kStepSleep);
|
||||
}
|
||||
|
||||
Progress = 1.0f;
|
||||
|
@ -89,13 +60,12 @@ namespace GitHub.Unity
|
|||
|
||||
Debug.LogFormat("{0} end", Label);
|
||||
|
||||
if(OnEnd != null)
|
||||
if (OnEnd != null)
|
||||
{
|
||||
OnEnd(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Abort()
|
||||
{
|
||||
Debug.LogFormat("Aborting {0}", Label);
|
||||
|
@ -103,27 +73,52 @@ namespace GitHub.Unity
|
|||
Done = true;
|
||||
}
|
||||
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
Abort();
|
||||
}
|
||||
|
||||
|
||||
public void Reconnect()
|
||||
{}
|
||||
|
||||
|
||||
public void WriteCache(TextWriter cache)
|
||||
{
|
||||
Debug.LogFormat("Writing cache for {0}", Label);
|
||||
cache.WriteLine("{");
|
||||
cache.WriteLine("\"{0}\": \"{1}\"", Tasks.TypeKey, CachedTask.TestTask);
|
||||
cache.WriteLine("}");
|
||||
}
|
||||
|
||||
cache.Write(
|
||||
@"{{
|
||||
""{0}"": ""{1}""
|
||||
}}",
|
||||
Tasks.TypeKey, CachedTask.TestTask
|
||||
);
|
||||
private static void Test(TestTask task)
|
||||
{
|
||||
EditorApplication.delayCall += () => Tasks.Add(task);
|
||||
}
|
||||
|
||||
public bool Blocking { get; protected set; }
|
||||
public float Progress { get; protected set; }
|
||||
public bool Done { get; protected set; }
|
||||
|
||||
public TaskQueueSetting Queued
|
||||
{
|
||||
get { return TaskQueueSetting.Queue; }
|
||||
}
|
||||
|
||||
public bool Critical
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public bool Cached
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public Action<ITask> OnBegin { get; set; }
|
||||
public Action<ITask> OnEnd { get; set; }
|
||||
|
||||
public string Label
|
||||
{
|
||||
get { return "Test task"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,57 +1,241 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
class Utility : ScriptableObject
|
||||
{
|
||||
public static readonly Regex
|
||||
ListBranchesRegex = new Regex(@"^(?<active>\*)?\s+(?<name>[\w\d\/\-\_]+)\s*(?:[a-z|0-9]{7} \[(?<tracking>[\w\d\/\-\_]+)\])?"),
|
||||
public const string StatusRenameDivider = "->";
|
||||
public static readonly Regex ListBranchesRegex =
|
||||
new Regex(@"^(?<active>\*)?\s+(?<name>[\w\d\/\-\_]+)\s*(?:[a-z|0-9]{7} \[(?<tracking>[\w\d\/\-\_]+)\])?"),
|
||||
ListRemotesRegex =
|
||||
new Regex(
|
||||
@"(?<name>[\w\d\-\_]+)\s+(?<url>https?:\/\/(?<login>(?<user>[\w\d]+)(?::(?<token>[\w\d]+))?)@(?<host>[\w\d\.\/\%]+))\s+\((?<function>fetch|push)\)"),
|
||||
LogCommitRegex = new Regex(@"commit\s(\S+)"),
|
||||
LogMergeRegex = new Regex(@"Merge:\s+(\S+)\s+(\S+)"),
|
||||
LogAuthorRegex = new Regex(@"Author:\s+(.+)\s<(.+)>"),
|
||||
LogTimeRegex = new Regex(@"Date:\s+(.+)"),
|
||||
LogDescriptionRegex = new Regex(@"^\s+(.+)"),
|
||||
StatusStartRegex = new Regex(@"(?<status>[AMRDC]|\?\?)(?:\d*)\s+(?<path>[\w\d\/\.\-_ \@]+)"),
|
||||
StatusEndRegex = new Regex(@"->\s(?<path>[\w\d\/\.\-_ ]+)"),
|
||||
StatusBranchLineValidRegex = new Regex(@"\#\#\s+(?:[\w\d\/\-_\.]+)"),
|
||||
StatusAheadBehindRegex =
|
||||
new Regex(
|
||||
@"\[ahead (?<ahead>\d+), behind (?<behind>\d+)\]|\[ahead (?<ahead>\d+)\]|\[behind (?<behind>\d+>)\]"),
|
||||
BranchNameRegex = new Regex(@"^(?<name>[\w\d\/\-\_]+)$");
|
||||
|
||||
ListRemotesRegex = new Regex(
|
||||
@"(?<name>[\w\d\-\_]+)\s+(?<url>https?:\/\/(?<login>(?<user>[\w\d]+)(?::(?<token>[\w\d]+))?)@(?<host>[\w\d\.\/\%]+))\s+\((?<function>fetch|push)\)"
|
||||
),
|
||||
private static bool ready;
|
||||
private static Action onReady;
|
||||
|
||||
LogCommitRegex = new Regex(@"commit\s(\S+)"),
|
||||
LogMergeRegex = new Regex(@"Merge:\s+(\S+)\s+(\S+)"),
|
||||
LogAuthorRegex = new Regex(@"Author:\s+(.+)\s<(.+)>"),
|
||||
LogTimeRegex = new Regex(@"Date:\s+(.+)"),
|
||||
LogDescriptionRegex = new Regex(@"^\s+(.+)"),
|
||||
|
||||
StatusStartRegex = new Regex(@"(?<status>[AMRDC]|\?\?)(?:\d*)\s+(?<path>[\w\d\/\.\-_ \@]+)"),
|
||||
StatusEndRegex = new Regex(@"->\s(?<path>[\w\d\/\.\-_ ]+)"),
|
||||
StatusBranchLineValidRegex = new Regex(@"\#\#\s+(?:[\w\d\/\-_\.]+)"),
|
||||
StatusAheadBehindRegex = new Regex(@"\[ahead (?<ahead>\d+), behind (?<behind>\d+)\]|\[ahead (?<ahead>\d+)\]|\[behind (?<behind>\d+>)\]"),
|
||||
|
||||
BranchNameRegex = new Regex(@"^(?<name>[\w\d\/\-\_]+)$");
|
||||
|
||||
public const string
|
||||
StatusRenameDivider = "->";
|
||||
|
||||
|
||||
static bool ready = false;
|
||||
static Action onReady;
|
||||
|
||||
|
||||
public static string GitInstallPath
|
||||
public static void RegisterReadyCallback(Action callback)
|
||||
{
|
||||
get
|
||||
onReady += callback;
|
||||
if (ready)
|
||||
{
|
||||
return Settings.Get("GitInstallPath");
|
||||
}
|
||||
set
|
||||
{
|
||||
Settings.Set("GitInstallPath", value);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
public static void UnregisterReadyCallback(Action callback)
|
||||
{
|
||||
onReady -= callback;
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
// Unity paths
|
||||
UnityAssetsPath = Application.dataPath;
|
||||
UnityProjectPath = UnityAssetsPath.Substring(0, UnityAssetsPath.Length - "Assets".Length - 1);
|
||||
|
||||
// Secure settings here so other threads don't try to reload
|
||||
Settings.Reload();
|
||||
|
||||
// Juggling to find out where we got installed
|
||||
var instance = FindObjectOfType(typeof(Utility)) as Utility;
|
||||
if (instance == null)
|
||||
{
|
||||
instance = CreateInstance<Utility>();
|
||||
}
|
||||
|
||||
var script = MonoScript.FromScriptableObject(instance);
|
||||
if (script == null)
|
||||
{
|
||||
ExtensionInstallPath = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtensionInstallPath = AssetDatabase.GetAssetPath(script);
|
||||
ExtensionInstallPath = ExtensionInstallPath.Substring(0, ExtensionInstallPath.LastIndexOf('/'));
|
||||
ExtensionInstallPath = ExtensionInstallPath.Substring(0, ExtensionInstallPath.LastIndexOf('/'));
|
||||
}
|
||||
|
||||
DestroyImmediate(instance);
|
||||
|
||||
// Evaluate project settings
|
||||
Issues = new List<ProjectConfigurationIssue>();
|
||||
EvaluateProjectConfigurationTask.UnregisterCallback(OnEvaluationResult);
|
||||
EvaluateProjectConfigurationTask.RegisterCallback(OnEvaluationResult);
|
||||
EvaluateProjectConfigurationTask.Schedule();
|
||||
|
||||
// Root paths
|
||||
if (string.IsNullOrEmpty(GitInstallPath) || !File.Exists(GitInstallPath))
|
||||
{
|
||||
FindGitTask.Schedule(path =>
|
||||
{
|
||||
Debug.Log("found " + path);
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
GitInstallPath = path;
|
||||
DetermineGitRoot();
|
||||
OnPrepareCompleted();
|
||||
}
|
||||
},
|
||||
() => Debug.Log("NOT FOUND")
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
DetermineGitRoot();
|
||||
OnPrepareCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
public static string RepositoryPathToAbsolute(string repositoryPath)
|
||||
{
|
||||
return Path.Combine(GitRoot, repositoryPath);
|
||||
}
|
||||
|
||||
public static string RepositoryPathToAsset(string repositoryPath)
|
||||
{
|
||||
var localDataPath = UnityAssetsPath.Substring(GitRoot.Length + 1);
|
||||
return repositoryPath.IndexOf(localDataPath) == 0
|
||||
? ("Assets" + repositoryPath.Substring(localDataPath.Length)).Replace(Path.DirectorySeparatorChar, '/')
|
||||
: null;
|
||||
}
|
||||
|
||||
public static string AssetPathToRepository(string assetPath)
|
||||
{
|
||||
var localDataPath = UnityAssetsPath.Substring(GitRoot.Length + 1);
|
||||
return Path.Combine(localDataPath.Substring(0, localDataPath.Length - "Assets".Length),
|
||||
assetPath.Replace('/', Path.DirectorySeparatorChar));
|
||||
}
|
||||
|
||||
public static void ParseLines(StringBuilder buffer, Action<string> lineParser, bool parseAll)
|
||||
{
|
||||
var end = buffer.Length - 1;
|
||||
|
||||
// Try to avoid partial lines unless asked not to
|
||||
if (!parseAll)
|
||||
{
|
||||
for (; end > 0 && buffer[end] != '\n'; --end) ;
|
||||
}
|
||||
|
||||
// Parse lines if we have any buffer to parse
|
||||
if (end > 0)
|
||||
{
|
||||
for (int index = 0, last = -1; index <= end; ++index)
|
||||
{
|
||||
if (buffer[index] == '\n')
|
||||
{
|
||||
var start = last + 1;
|
||||
// TODO: Figure out how we get out of doing that ToString call
|
||||
var line = buffer.ToString(start, index - start);
|
||||
lineParser(line);
|
||||
last = index;
|
||||
}
|
||||
}
|
||||
|
||||
buffer.Remove(0, end + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static Texture2D GetIcon(string filename, string filename2x = "")
|
||||
{
|
||||
if (EditorGUIUtility.pixelsPerPoint > 1f && !string.IsNullOrEmpty(filename2x))
|
||||
{
|
||||
filename = filename2x;
|
||||
}
|
||||
|
||||
return Assembly.GetExecutingAssembly().GetManifestResourceStream(filename).ToTexture2D();
|
||||
}
|
||||
|
||||
// Based on: https://www.rosettacode.org/wiki/Find_common_directory_path#C.23
|
||||
public static string FindCommonPath(string separator, IEnumerable<string> paths)
|
||||
{
|
||||
var commonPath = string.Empty;
|
||||
var separatedPath =
|
||||
paths.First(first => first.Length == paths.Max(second => second.Length))
|
||||
.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.ToList();
|
||||
|
||||
foreach (var pathSegment in separatedPath.AsEnumerable())
|
||||
{
|
||||
var pathExtension = pathSegment + separator;
|
||||
|
||||
if (commonPath.Length == 0 && paths.All(path => path.StartsWith(pathExtension)))
|
||||
{
|
||||
commonPath = pathExtension;
|
||||
}
|
||||
else if (paths.All(path => path.StartsWith(commonPath + pathExtension)))
|
||||
{
|
||||
commonPath += pathExtension;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return commonPath;
|
||||
}
|
||||
|
||||
private static void OnPrepareCompleted()
|
||||
{
|
||||
ready = true;
|
||||
if (onReady != null)
|
||||
{
|
||||
onReady();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnEvaluationResult(IEnumerable<ProjectConfigurationIssue> result)
|
||||
{
|
||||
Issues = new List<ProjectConfigurationIssue>(result);
|
||||
}
|
||||
|
||||
private static void DetermineGitRoot()
|
||||
{
|
||||
GitRoot = FindRoot(UnityAssetsPath);
|
||||
}
|
||||
|
||||
// TODO: replace with libgit2sharp call
|
||||
private static string FindRoot(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Path.GetDirectoryName(path)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Directory.Exists(Path.Combine(path, ".git")))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
return FindRoot(Directory.GetParent(path).FullName);
|
||||
}
|
||||
|
||||
public static string GitInstallPath
|
||||
{
|
||||
get { return Settings.Get("GitInstallPath"); }
|
||||
set { Settings.Set("GitInstallPath", value); }
|
||||
}
|
||||
|
||||
public static string GitRoot { get; protected set; }
|
||||
public static string UnityAssetsPath { get; protected set; }
|
||||
|
@ -59,25 +243,16 @@ namespace GitHub.Unity
|
|||
public static string ExtensionInstallPath { get; protected set; }
|
||||
public static List<ProjectConfigurationIssue> Issues { get; protected set; }
|
||||
|
||||
|
||||
public static bool GitFound
|
||||
{
|
||||
get
|
||||
{
|
||||
return !string.IsNullOrEmpty(GitInstallPath);
|
||||
}
|
||||
get { return !string.IsNullOrEmpty(GitInstallPath); }
|
||||
}
|
||||
|
||||
|
||||
public static bool ActiveRepository
|
||||
{
|
||||
get
|
||||
{
|
||||
return !string.IsNullOrEmpty(GitRoot);
|
||||
}
|
||||
get { return !string.IsNullOrEmpty(GitRoot); }
|
||||
}
|
||||
|
||||
|
||||
public static bool IsWindows
|
||||
{
|
||||
get
|
||||
|
@ -95,218 +270,7 @@ namespace GitHub.Unity
|
|||
|
||||
public static bool IsDevelopmentBuild
|
||||
{
|
||||
get
|
||||
{
|
||||
return File.Exists(Path.Combine(UnityProjectPath.Replace('/', Path.DirectorySeparatorChar), ".devroot"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void RegisterReadyCallback(Action callback)
|
||||
{
|
||||
onReady += callback;
|
||||
if (ready)
|
||||
{
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void UnregisterReadyCallback(Action callback)
|
||||
{
|
||||
onReady -= callback;
|
||||
}
|
||||
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
// Unity paths
|
||||
UnityAssetsPath = Application.dataPath;
|
||||
UnityProjectPath = UnityAssetsPath.Substring(0, UnityAssetsPath.Length - "Assets".Length - 1);
|
||||
|
||||
// Secure settings here so other threads don't try to reload
|
||||
Settings.Reload();
|
||||
|
||||
// Juggling to find out where we got installed
|
||||
Utility instance = FindObjectOfType(typeof(Utility)) as Utility;
|
||||
if (instance == null)
|
||||
{
|
||||
instance = CreateInstance<Utility>();
|
||||
}
|
||||
MonoScript script = MonoScript.FromScriptableObject(instance);
|
||||
if (script == null)
|
||||
{
|
||||
ExtensionInstallPath = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtensionInstallPath = AssetDatabase.GetAssetPath(script);
|
||||
ExtensionInstallPath = ExtensionInstallPath.Substring(0, ExtensionInstallPath.LastIndexOf('/'));
|
||||
ExtensionInstallPath = ExtensionInstallPath.Substring(0, ExtensionInstallPath.LastIndexOf('/'));
|
||||
}
|
||||
DestroyImmediate(instance);
|
||||
|
||||
// Evaluate project settings
|
||||
Issues = new List<ProjectConfigurationIssue>();
|
||||
EvaluateProjectConfigurationTask.UnregisterCallback(OnEvaluationResult);
|
||||
EvaluateProjectConfigurationTask.RegisterCallback(OnEvaluationResult);
|
||||
EvaluateProjectConfigurationTask.Schedule();
|
||||
|
||||
// Root paths
|
||||
if (string.IsNullOrEmpty(GitInstallPath) || !File.Exists(GitInstallPath))
|
||||
{
|
||||
FindGitTask.Schedule(path =>
|
||||
{
|
||||
Debug.Log("found " + path);
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
GitInstallPath = path;
|
||||
DetermineGitRoot();
|
||||
OnPrepareCompleted();
|
||||
}
|
||||
},
|
||||
() =>
|
||||
{
|
||||
Debug.Log("NOT FOUND");
|
||||
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
DetermineGitRoot();
|
||||
OnPrepareCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void OnPrepareCompleted()
|
||||
{
|
||||
ready = true;
|
||||
if (onReady != null)
|
||||
{
|
||||
onReady();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void OnEvaluationResult(IEnumerable<ProjectConfigurationIssue> result)
|
||||
{
|
||||
Issues = new List<ProjectConfigurationIssue>(result);
|
||||
}
|
||||
|
||||
|
||||
static void DetermineGitRoot()
|
||||
{
|
||||
GitRoot = FindRoot(UnityAssetsPath);
|
||||
}
|
||||
|
||||
|
||||
// TODO: replace with libgit2sharp call
|
||||
static string FindRoot(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Path.GetDirectoryName(path)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Directory.Exists(Path.Combine(path, ".git")))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
return FindRoot(Directory.GetParent(path).FullName);
|
||||
}
|
||||
|
||||
|
||||
public static string RepositoryPathToAbsolute(string repositoryPath)
|
||||
{
|
||||
return Path.Combine(Utility.GitRoot, repositoryPath);
|
||||
}
|
||||
|
||||
|
||||
public static string RepositoryPathToAsset(string repositoryPath)
|
||||
{
|
||||
string localDataPath = UnityAssetsPath.Substring(GitRoot.Length + 1);
|
||||
return (repositoryPath.IndexOf(localDataPath) == 0) ?
|
||||
("Assets" + repositoryPath.Substring(localDataPath.Length)).Replace(Path.DirectorySeparatorChar, '/') :
|
||||
null;
|
||||
}
|
||||
|
||||
|
||||
public static string AssetPathToRepository(string assetPath)
|
||||
{
|
||||
string localDataPath = UnityAssetsPath.Substring(GitRoot.Length + 1);
|
||||
return Path.Combine(localDataPath.Substring(0, localDataPath.Length - "Assets".Length), assetPath.Replace('/', Path.DirectorySeparatorChar));
|
||||
}
|
||||
|
||||
|
||||
public static void ParseLines(StringBuilder buffer, Action<string> lineParser, bool parseAll)
|
||||
{
|
||||
int end = buffer.Length - 1;
|
||||
|
||||
if (!parseAll)
|
||||
// Try to avoid partial lines unless asked not to
|
||||
{
|
||||
for (; end > 0 && buffer[end] != '\n'; --end) ;
|
||||
}
|
||||
|
||||
if (end > 0)
|
||||
// Parse lines if we have any buffer to parse
|
||||
{
|
||||
for (int index = 0, last = -1; index <= end; ++index)
|
||||
{
|
||||
if (buffer[index] == '\n')
|
||||
{
|
||||
int start = last + 1;
|
||||
// TODO: Figure out how we get out of doing that ToString call
|
||||
string line = buffer.ToString(start, index - start);
|
||||
lineParser(line);
|
||||
last = index;
|
||||
}
|
||||
}
|
||||
|
||||
buffer.Remove(0, end + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Texture2D GetIcon(string filename, string filename2x = "")
|
||||
{
|
||||
if (EditorGUIUtility.pixelsPerPoint > 1f && !string.IsNullOrEmpty(filename2x))
|
||||
{
|
||||
filename = filename2x;
|
||||
}
|
||||
|
||||
return Assembly.GetExecutingAssembly().GetManifestResourceStream(filename).ToTexture2D();
|
||||
}
|
||||
|
||||
// Based on: https://www.rosettacode.org/wiki/Find_common_directory_path#C.23
|
||||
public static string FindCommonPath(string separator, IEnumerable<string> paths)
|
||||
{
|
||||
string commonPath = string.Empty;
|
||||
List<string> separatedPath = paths
|
||||
.First(first => first.Length == paths.Max(second => second.Length))
|
||||
.Split(new string[] { separator }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.ToList();
|
||||
|
||||
foreach (string pathSegment in separatedPath.AsEnumerable())
|
||||
{
|
||||
string pathExtension = pathSegment + separator;
|
||||
|
||||
if (commonPath.Length == 0 && paths.All(path => path.StartsWith(pathExtension)))
|
||||
{
|
||||
commonPath = pathExtension;
|
||||
}
|
||||
else if (paths.All(path => path.StartsWith(commonPath + pathExtension)))
|
||||
{
|
||||
commonPath += pathExtension;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return commonPath;
|
||||
get { return File.Exists(Path.Combine(UnityProjectPath.Replace('/', Path.DirectorySeparatorChar), ".devroot")); }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -314,14 +278,15 @@ namespace GitHub.Unity
|
|||
{
|
||||
public static Texture2D ToTexture2D(this Stream input)
|
||||
{
|
||||
byte[] buffer = new byte[16 * 1024];
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
var buffer = new byte[16 * 1024];
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
int read;
|
||||
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
ms.Write(buffer, 0, read);
|
||||
}
|
||||
|
||||
var tex = new Texture2D(1, 1);
|
||||
tex.LoadImage(ms.ToArray());
|
||||
return tex;
|
||||
|
|
|
@ -1,102 +1,43 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
[System.Serializable]
|
||||
[Serializable]
|
||||
class BranchesView : Subview
|
||||
{
|
||||
enum NodeType
|
||||
{
|
||||
Folder,
|
||||
LocalBranch,
|
||||
RemoteBranch
|
||||
}
|
||||
private const string ConfirmSwitchTitle = "Confirm branch switch";
|
||||
private const string ConfirmSwitchMessage = "Switch branch to {0}?";
|
||||
private const string ConfirmSwitchOK = "Switch";
|
||||
private const string ConfirmSwitchCancel = "Cancel";
|
||||
private const string NewBranchCancelButton = "x";
|
||||
private const string NewBranchConfirmButton = "Create";
|
||||
private const string FavouritesSetting = "Favourites";
|
||||
private const string FavouritesTitle = "FAVOURITES";
|
||||
private const string LocalTitle = "LOCAL BRANCHES";
|
||||
private const string RemoteTitle = "REMOTE BRANCHES";
|
||||
private const string CreateBranchButton = "+ New branch";
|
||||
|
||||
[NonSerialized] private List<BranchTreeNode> favourites = new List<BranchTreeNode>();
|
||||
[NonSerialized] private int listID = -1;
|
||||
[NonSerialized] private List<GitBranch> newLocalBranches;
|
||||
[NonSerialized] private BranchTreeNode newNodeSelection;
|
||||
[NonSerialized] private BranchesMode targetMode;
|
||||
|
||||
enum BranchesMode
|
||||
{
|
||||
Default,
|
||||
Create
|
||||
}
|
||||
|
||||
|
||||
[Serializable]
|
||||
class BranchTreeNode
|
||||
{
|
||||
List<BranchTreeNode> children = new List<BranchTreeNode>();
|
||||
|
||||
|
||||
public string Label;
|
||||
public BranchTreeNode Tracking;
|
||||
|
||||
|
||||
public string Name { get; protected set; }
|
||||
public NodeType Type { get; protected set; }
|
||||
public bool Active { get; protected set; }
|
||||
public IList<BranchTreeNode> Children { get { return children; } }
|
||||
|
||||
|
||||
public BranchTreeNode(string name, NodeType type, bool active)
|
||||
{
|
||||
Label = Name = name;
|
||||
Type = type;
|
||||
Active = active;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct Remote
|
||||
{
|
||||
// TODO: Pull in and store more data from GitListRemotesTask
|
||||
public string Name;
|
||||
public BranchTreeNode Root;
|
||||
}
|
||||
|
||||
|
||||
const string
|
||||
ConfirmSwitchTitle = "Confirm branch switch",
|
||||
ConfirmSwitchMessage = "Switch branch to {0}?",
|
||||
ConfirmSwitchOK = "Switch",
|
||||
ConfirmSwitchCancel = "Cancel",
|
||||
NewBranchCancelButton = "x",
|
||||
NewBranchConfirmButton = "Create",
|
||||
FavouritesSetting = "Favourites",
|
||||
FavouritesTitle = "FAVOURITES",
|
||||
LocalTitle = "LOCAL BRANCHES",
|
||||
RemoteTitle = "REMOTE BRANCHES",
|
||||
CreateBranchButton = "+ New branch";
|
||||
|
||||
|
||||
[SerializeField] Vector2 scroll;
|
||||
[SerializeField] BranchTreeNode localRoot;
|
||||
[SerializeField] List<Remote> remotes = new List<Remote>();
|
||||
[SerializeField] BranchTreeNode selectedNode = null;
|
||||
[SerializeField] BranchTreeNode activeBranchNode = null;
|
||||
[SerializeField] BranchesMode mode = BranchesMode.Default;
|
||||
[SerializeField] string newBranchName;
|
||||
|
||||
|
||||
BranchTreeNode newNodeSelection = null;
|
||||
List<GitBranch> newLocalBranches;
|
||||
List<BranchTreeNode> favourites = new List<BranchTreeNode>();
|
||||
int listID = -1;
|
||||
BranchesMode targetMode;
|
||||
|
||||
|
||||
protected override void OnShow()
|
||||
{
|
||||
targetMode = mode;
|
||||
}
|
||||
|
||||
[SerializeField] private BranchTreeNode activeBranchNode;
|
||||
[SerializeField] private BranchTreeNode localRoot;
|
||||
[SerializeField] private BranchesMode mode = BranchesMode.Default;
|
||||
[SerializeField] private string newBranchName;
|
||||
[SerializeField] private List<Remote> remotes = new List<Remote>();
|
||||
[SerializeField] private Vector2 scroll;
|
||||
[SerializeField] private BranchTreeNode selectedNode;
|
||||
|
||||
public override void Refresh()
|
||||
{
|
||||
HistoryView historyView = ((Window)parent).HistoryTab;
|
||||
var historyView = ((Window)parent).HistoryTab;
|
||||
|
||||
if (historyView.BroadMode)
|
||||
{
|
||||
|
@ -108,220 +49,15 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public void RefreshEmbedded()
|
||||
{
|
||||
GitListBranchesTask.ScheduleLocal(OnLocalBranchesUpdate);
|
||||
GitListBranchesTask.ScheduleRemote(OnRemoteBranchesUpdate);
|
||||
}
|
||||
|
||||
|
||||
void OnLocalBranchesUpdate(IEnumerable<GitBranch> list)
|
||||
{
|
||||
newLocalBranches = new List<GitBranch>(list);
|
||||
}
|
||||
|
||||
|
||||
void OnRemoteBranchesUpdate(IEnumerable<GitBranch> list)
|
||||
{
|
||||
BuildTree(newLocalBranches, list);
|
||||
newLocalBranches.Clear();
|
||||
}
|
||||
|
||||
|
||||
void BuildTree(IEnumerable<GitBranch> local, IEnumerable<GitBranch> remote)
|
||||
{
|
||||
// Sort
|
||||
List<GitBranch>
|
||||
localBranches = new List<GitBranch>(local),
|
||||
remoteBranches = new List<GitBranch>(remote);
|
||||
localBranches.Sort(CompareBranches);
|
||||
remoteBranches.Sort(CompareBranches);
|
||||
|
||||
// Prepare for tracking
|
||||
List<KeyValuePair<int, int>> tracking = new List<KeyValuePair<int, int>>();
|
||||
List<BranchTreeNode> localBranchNodes = new List<BranchTreeNode>();
|
||||
|
||||
// Prepare for updated favourites listing
|
||||
favourites.Clear();
|
||||
|
||||
// Just build directly on the local root, keep track of active branch
|
||||
localRoot = new BranchTreeNode("", NodeType.Folder, false);
|
||||
for (int index = 0; index < localBranches.Count; ++index)
|
||||
{
|
||||
GitBranch branch = localBranches[index];
|
||||
BranchTreeNode node = new BranchTreeNode(branch.Name, NodeType.LocalBranch, branch.Active);
|
||||
localBranchNodes.Add(node);
|
||||
|
||||
// Keep active node for quick reference
|
||||
if (branch.Active)
|
||||
{
|
||||
activeBranchNode = node;
|
||||
}
|
||||
|
||||
// Add to tracking
|
||||
if (!string.IsNullOrEmpty(branch.Tracking))
|
||||
{
|
||||
int trackingIndex = !remoteBranches.Any() ? -1 :
|
||||
Enumerable.Range(1, remoteBranches.Count + 1).FirstOrDefault(
|
||||
i => remoteBranches[i - 1].Name.Equals(branch.Tracking)
|
||||
) - 1;
|
||||
|
||||
if (trackingIndex > -1)
|
||||
{
|
||||
tracking.Add(new KeyValuePair<int, int>(index, trackingIndex));
|
||||
}
|
||||
}
|
||||
|
||||
// Add to favourites
|
||||
if (Settings.GetElementIndex(FavouritesSetting, branch.Name) > -1)
|
||||
{
|
||||
favourites.Add(node);
|
||||
}
|
||||
|
||||
// Build into tree
|
||||
BuildTree(localRoot, node);
|
||||
}
|
||||
|
||||
// Maintain list of remotes before building their roots, ignoring active state
|
||||
remotes.Clear();
|
||||
for (int index = 0; index < remoteBranches.Count; ++index)
|
||||
{
|
||||
GitBranch branch = remoteBranches[index];
|
||||
|
||||
// Remote name is always the first level
|
||||
string remoteName = branch.Name.Substring(0, branch.Name.IndexOf('/'));
|
||||
|
||||
// Get or create this remote
|
||||
int remoteIndex = Enumerable.Range(1, remotes.Count + 1).FirstOrDefault(i => remotes.Count > i - 1 && remotes[i - 1].Name.Equals(remoteName)) - 1;
|
||||
if (remoteIndex < 0)
|
||||
{
|
||||
remotes.Add(new Remote() { Name = remoteName, Root = new BranchTreeNode("", NodeType.Folder, false) });
|
||||
remoteIndex = remotes.Count - 1;
|
||||
}
|
||||
|
||||
// Create the branch
|
||||
BranchTreeNode node = new BranchTreeNode(branch.Name, NodeType.RemoteBranch, false) { Label = branch.Name.Substring(remoteName.Length + 1) };
|
||||
|
||||
// Establish tracking link
|
||||
for (int trackingIndex = 0; trackingIndex < tracking.Count; ++trackingIndex)
|
||||
{
|
||||
KeyValuePair<int, int> pair = tracking[trackingIndex];
|
||||
|
||||
if (pair.Value == index)
|
||||
{
|
||||
localBranchNodes[pair.Key].Tracking = node;
|
||||
}
|
||||
}
|
||||
|
||||
// Add to favourites
|
||||
if (Settings.GetElementIndex(FavouritesSetting, branch.Name) > -1)
|
||||
{
|
||||
favourites.Add(node);
|
||||
}
|
||||
|
||||
// Build on the root of the remote, just like with locals
|
||||
BuildTree(remotes[remoteIndex].Root, node);
|
||||
}
|
||||
|
||||
Repaint();
|
||||
}
|
||||
|
||||
|
||||
static int CompareBranches(GitBranch a, GitBranch b)
|
||||
{
|
||||
if (GetFavourite(a.Name))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (GetFavourite(b.Name))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a.Name.Equals("master"))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (b.Name.Equals("master"))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void BuildTree(BranchTreeNode parent, BranchTreeNode child)
|
||||
{
|
||||
int firstSplit = child.Label.IndexOf('/');
|
||||
|
||||
// No nesting needed here, this is just a straight add
|
||||
if (firstSplit < 0)
|
||||
{
|
||||
parent.Children.Add(child);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get or create the next folder level
|
||||
string folderName = child.Label.Substring(0, firstSplit);
|
||||
BranchTreeNode folder = parent.Children.FirstOrDefault(f => f.Label.Equals(folderName));
|
||||
if (folder == null)
|
||||
{
|
||||
folder = new BranchTreeNode("", NodeType.Folder, false) { Label = folderName };
|
||||
parent.Children.Add(folder);
|
||||
}
|
||||
|
||||
// Pop the folder name from the front of the child label and add it to the folder
|
||||
child.Label = child.Label.Substring(folderName.Length + 1);
|
||||
BuildTree(folder, child);
|
||||
}
|
||||
|
||||
|
||||
static bool GetFavourite(BranchTreeNode branch)
|
||||
{
|
||||
return GetFavourite(branch.Name);
|
||||
}
|
||||
|
||||
|
||||
static bool GetFavourite(string branchName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(branchName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Settings.GetElementIndex(FavouritesSetting, branchName) > -1;
|
||||
}
|
||||
|
||||
|
||||
void SetFavourite(BranchTreeNode branch, bool favourite)
|
||||
{
|
||||
if (string.IsNullOrEmpty(branch.Name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!favourite)
|
||||
{
|
||||
Settings.RemoveElement(FavouritesSetting, branch.Name);
|
||||
favourites.Remove(branch);
|
||||
}
|
||||
else
|
||||
{
|
||||
Settings.RemoveElement(FavouritesSetting, branch.Name, false);
|
||||
Settings.AddElement(FavouritesSetting, branch.Name);
|
||||
favourites.Remove(branch);
|
||||
favourites.Add(branch);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
HistoryView historyView = ((Window)parent).HistoryTab;
|
||||
var historyView = ((Window)parent).HistoryTab;
|
||||
|
||||
if (historyView.BroadMode)
|
||||
{
|
||||
|
@ -341,6 +77,7 @@ namespace GitHub.Unity
|
|||
public void OnEmbeddedGUI()
|
||||
{
|
||||
scroll = GUILayout.BeginScrollView(scroll);
|
||||
{
|
||||
listID = GUIUtility.GetControlID(FocusType.Keyboard);
|
||||
|
||||
// Favourites list
|
||||
|
@ -348,13 +85,19 @@ namespace GitHub.Unity
|
|||
{
|
||||
GUILayout.Label(FavouritesTitle);
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.Space(Styles.BranchListIndentation);
|
||||
GUILayout.BeginVertical();
|
||||
for (int index = 0; index < favourites.Count; ++index)
|
||||
{
|
||||
for (var index = 0; index < favourites.Count; ++index)
|
||||
{
|
||||
OnTreeNodeGUI(favourites[index]);
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(Styles.BranchListSeperation);
|
||||
|
@ -363,14 +106,18 @@ namespace GitHub.Unity
|
|||
// Local branches and "create branch" button
|
||||
GUILayout.Label(LocalTitle);
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.Space(Styles.BranchListIndentation);
|
||||
GUILayout.BeginVertical();
|
||||
{
|
||||
OnTreeNodeChildrenGUI(localRoot);
|
||||
|
||||
GUILayout.Space(EditorGUIUtility.standardVerticalSpacing);
|
||||
|
||||
OnCreateGUI();
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(Styles.BranchListSeperation);
|
||||
|
@ -378,25 +125,35 @@ namespace GitHub.Unity
|
|||
// Remotes
|
||||
GUILayout.Label(RemoteTitle);
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.Space(Styles.BranchListIndentation);
|
||||
GUILayout.BeginVertical();
|
||||
for (int index = 0; index < remotes.Count; ++index)
|
||||
for (var index = 0; index < remotes.Count; ++index)
|
||||
{
|
||||
var remote = remotes[index];
|
||||
GUILayout.Label(remote.Name);
|
||||
|
||||
// Branches of the remote
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
Remote remote = remotes[index];
|
||||
GUILayout.Label(remote.Name);
|
||||
|
||||
// Branches of the remote
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Space(Styles.TreeIndentation);
|
||||
GUILayout.BeginVertical();
|
||||
OnTreeNodeChildrenGUI(remote.Root);
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(Styles.BranchListSeperation);
|
||||
GUILayout.Space(Styles.TreeIndentation);
|
||||
GUILayout.BeginVertical();
|
||||
{
|
||||
OnTreeNodeChildrenGUI(remote.Root);
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(Styles.BranchListSeperation);
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
|
@ -425,28 +182,230 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void OnCreateGUI()
|
||||
private static int CompareBranches(GitBranch a, GitBranch b)
|
||||
{
|
||||
if (GetFavourite(a.Name))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (GetFavourite(b.Name))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a.Name.Equals("master"))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (b.Name.Equals("master"))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static bool GetFavourite(BranchTreeNode branch)
|
||||
{
|
||||
return GetFavourite(branch.Name);
|
||||
}
|
||||
|
||||
private static bool GetFavourite(string branchName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(branchName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Settings.GetElementIndex(FavouritesSetting, branchName) > -1;
|
||||
}
|
||||
|
||||
protected override void OnShow()
|
||||
{
|
||||
targetMode = mode;
|
||||
}
|
||||
|
||||
private void OnLocalBranchesUpdate(IEnumerable<GitBranch> list)
|
||||
{
|
||||
newLocalBranches = new List<GitBranch>(list);
|
||||
}
|
||||
|
||||
private void OnRemoteBranchesUpdate(IEnumerable<GitBranch> list)
|
||||
{
|
||||
BuildTree(newLocalBranches, list);
|
||||
newLocalBranches.Clear();
|
||||
}
|
||||
|
||||
private void BuildTree(IEnumerable<GitBranch> local, IEnumerable<GitBranch> remote)
|
||||
{
|
||||
// Sort
|
||||
var localBranches = new List<GitBranch>(local);
|
||||
var remoteBranches = new List<GitBranch>(remote);
|
||||
localBranches.Sort(CompareBranches);
|
||||
remoteBranches.Sort(CompareBranches);
|
||||
|
||||
// Prepare for tracking
|
||||
var tracking = new List<KeyValuePair<int, int>>();
|
||||
var localBranchNodes = new List<BranchTreeNode>();
|
||||
|
||||
// Prepare for updated favourites listing
|
||||
favourites.Clear();
|
||||
|
||||
// Just build directly on the local root, keep track of active branch
|
||||
localRoot = new BranchTreeNode("", NodeType.Folder, false);
|
||||
for (var index = 0; index < localBranches.Count; ++index)
|
||||
{
|
||||
var branch = localBranches[index];
|
||||
var node = new BranchTreeNode(branch.Name, NodeType.LocalBranch, branch.Active);
|
||||
localBranchNodes.Add(node);
|
||||
|
||||
// Keep active node for quick reference
|
||||
if (branch.Active)
|
||||
{
|
||||
activeBranchNode = node;
|
||||
}
|
||||
|
||||
// Add to tracking
|
||||
if (!string.IsNullOrEmpty(branch.Tracking))
|
||||
{
|
||||
var trackingIndex = !remoteBranches.Any()
|
||||
? -1
|
||||
: Enumerable.Range(0, remoteBranches.Count).FirstOrDefault(i => remoteBranches[i].Name.Equals(branch.Tracking));
|
||||
|
||||
if (trackingIndex > -1)
|
||||
{
|
||||
tracking.Add(new KeyValuePair<int, int>(index, trackingIndex));
|
||||
}
|
||||
}
|
||||
|
||||
// Add to favourites
|
||||
if (Settings.GetElementIndex(FavouritesSetting, branch.Name) > -1)
|
||||
{
|
||||
favourites.Add(node);
|
||||
}
|
||||
|
||||
// Build into tree
|
||||
BuildTree(localRoot, node);
|
||||
}
|
||||
|
||||
// Maintain list of remotes before building their roots, ignoring active state
|
||||
remotes.Clear();
|
||||
for (var index = 0; index < remoteBranches.Count; ++index)
|
||||
{
|
||||
var branch = remoteBranches[index];
|
||||
|
||||
// Remote name is always the first level
|
||||
var remoteName = branch.Name.Substring(0, branch.Name.IndexOf('/'));
|
||||
|
||||
// Get or create this remote
|
||||
var remoteIndex = Enumerable.Range(1, remotes.Count + 1)
|
||||
.FirstOrDefault(i => remotes.Count > i - 1 && remotes[i - 1].Name.Equals(remoteName)) - 1;
|
||||
if (remoteIndex < 0)
|
||||
{
|
||||
remotes.Add(new Remote { Name = remoteName, Root = new BranchTreeNode("", NodeType.Folder, false) });
|
||||
remoteIndex = remotes.Count - 1;
|
||||
}
|
||||
|
||||
// Create the branch
|
||||
var node = new BranchTreeNode(branch.Name, NodeType.RemoteBranch, false) {
|
||||
Label = branch.Name.Substring(remoteName.Length + 1)
|
||||
};
|
||||
|
||||
// Establish tracking link
|
||||
for (var trackingIndex = 0; trackingIndex < tracking.Count; ++trackingIndex)
|
||||
{
|
||||
var pair = tracking[trackingIndex];
|
||||
|
||||
if (pair.Value == index)
|
||||
{
|
||||
localBranchNodes[pair.Key].Tracking = node;
|
||||
}
|
||||
}
|
||||
|
||||
// Add to favourites
|
||||
if (Settings.GetElementIndex(FavouritesSetting, branch.Name) > -1)
|
||||
{
|
||||
favourites.Add(node);
|
||||
}
|
||||
|
||||
// Build on the root of the remote, just like with locals
|
||||
BuildTree(remotes[remoteIndex].Root, node);
|
||||
}
|
||||
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private void BuildTree(BranchTreeNode parent, BranchTreeNode child)
|
||||
{
|
||||
var firstSplit = child.Label.IndexOf('/');
|
||||
|
||||
// No nesting needed here, this is just a straight add
|
||||
if (firstSplit < 0)
|
||||
{
|
||||
parent.Children.Add(child);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get or create the next folder level
|
||||
var folderName = child.Label.Substring(0, firstSplit);
|
||||
var folder = parent.Children.FirstOrDefault(f => f.Label.Equals(folderName));
|
||||
if (folder == null)
|
||||
{
|
||||
folder = new BranchTreeNode("", NodeType.Folder, false) { Label = folderName };
|
||||
parent.Children.Add(folder);
|
||||
}
|
||||
|
||||
// Pop the folder name from the front of the child label and add it to the folder
|
||||
child.Label = child.Label.Substring(folderName.Length + 1);
|
||||
BuildTree(folder, child);
|
||||
}
|
||||
|
||||
private void SetFavourite(BranchTreeNode branch, bool favourite)
|
||||
{
|
||||
if (string.IsNullOrEmpty(branch.Name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!favourite)
|
||||
{
|
||||
Settings.RemoveElement(FavouritesSetting, branch.Name);
|
||||
favourites.Remove(branch);
|
||||
}
|
||||
else
|
||||
{
|
||||
Settings.RemoveElement(FavouritesSetting, branch.Name, false);
|
||||
Settings.AddElement(FavouritesSetting, branch.Name);
|
||||
favourites.Remove(branch);
|
||||
favourites.Add(branch);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCreateGUI()
|
||||
{
|
||||
if (mode == BranchesMode.Default)
|
||||
// Create button
|
||||
if (mode == BranchesMode.Default)
|
||||
{
|
||||
if (GUILayout.Button(CreateBranchButton, GUI.skin.label, GUILayout.ExpandWidth(false)))
|
||||
{
|
||||
targetMode = BranchesMode.Create;
|
||||
}
|
||||
}
|
||||
else if (mode == BranchesMode.Create)
|
||||
// Branch name + cancel + create
|
||||
else if (mode == BranchesMode.Create)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
bool
|
||||
createBranch = false,
|
||||
cancelCreate = false,
|
||||
cannotCreate = selectedNode == null || selectedNode.Type == NodeType.Folder || !Utility.BranchNameRegex.IsMatch(newBranchName);
|
||||
{
|
||||
var createBranch = false;
|
||||
var cancelCreate = false;
|
||||
var cannotCreate = selectedNode == null ||
|
||||
selectedNode.Type == NodeType.Folder ||
|
||||
!Utility.BranchNameRegex.IsMatch(newBranchName);
|
||||
|
||||
// Create on return/enter or cancel on escape
|
||||
int offsetID = GUIUtility.GetControlID(FocusType.Passive);
|
||||
var offsetID = GUIUtility.GetControlID(FocusType.Passive);
|
||||
if (Event.current.isKey && GUIUtility.keyboardControl == offsetID + 1)
|
||||
{
|
||||
if (Event.current.keyCode == KeyCode.Escape)
|
||||
|
@ -478,10 +437,12 @@ namespace GitHub.Unity
|
|||
|
||||
// Create
|
||||
EditorGUI.BeginDisabledGroup(cannotCreate);
|
||||
{
|
||||
if (GUILayout.Button(NewBranchConfirmButton, EditorStyles.miniButtonRight, GUILayout.ExpandWidth(false)))
|
||||
{
|
||||
createBranch = true;
|
||||
}
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
// Effectuate create
|
||||
|
@ -497,23 +458,22 @@ namespace GitHub.Unity
|
|||
GUIUtility.keyboardControl = -1;
|
||||
targetMode = BranchesMode.Default;
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OnTreeNodeGUI(BranchTreeNode node)
|
||||
private void OnTreeNodeGUI(BranchTreeNode node)
|
||||
{
|
||||
// Content, style, and rects
|
||||
GUIContent content = new GUIContent(node.Label, node.Children.Count > 0 ? Styles.FolderIcon : Styles.DefaultAssetIcon);
|
||||
GUIStyle style = node.Active ? Styles.BoldLabel : Styles.Label;
|
||||
Rect rect = GUILayoutUtility.GetRect(content, style, GUILayout.MaxHeight(EditorGUIUtility.singleLineHeight));
|
||||
Rect clickRect = new Rect(0f, rect.y, position.width, rect.height);
|
||||
Rect favouriteRect = new Rect(clickRect.xMax - clickRect.height * 2f, clickRect.y, clickRect.height, clickRect.height);
|
||||
var content = new GUIContent(node.Label, node.Children.Count > 0 ? Styles.FolderIcon : Styles.DefaultAssetIcon);
|
||||
var style = node.Active ? Styles.BoldLabel : Styles.Label;
|
||||
var rect = GUILayoutUtility.GetRect(content, style, GUILayout.MaxHeight(EditorGUIUtility.singleLineHeight));
|
||||
var clickRect = new Rect(0f, rect.y, position.width, rect.height);
|
||||
var favouriteRect = new Rect(clickRect.xMax - clickRect.height * 2f, clickRect.y, clickRect.height, clickRect.height);
|
||||
|
||||
bool
|
||||
selected = selectedNode == node,
|
||||
keyboardFocus = GUIUtility.keyboardControl == listID;
|
||||
var selected = selectedNode == node;
|
||||
var keyboardFocus = GUIUtility.keyboardControl == listID;
|
||||
|
||||
// Selection highlight and favourite toggle
|
||||
if (selected)
|
||||
|
@ -525,7 +485,7 @@ namespace GitHub.Unity
|
|||
|
||||
if (node.Type != NodeType.Folder)
|
||||
{
|
||||
bool favourite = GetFavourite(node);
|
||||
var favourite = GetFavourite(node);
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
GUI.DrawTexture(favouriteRect, favourite ? Styles.FavouriteIconOn : Styles.FavouriteIconOff);
|
||||
|
@ -552,20 +512,20 @@ namespace GitHub.Unity
|
|||
// State marks
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
Rect indicatorRect = new Rect(rect.x - rect.height, rect.y, rect.height, rect.height);
|
||||
var indicatorRect = new Rect(rect.x - rect.height, rect.y, rect.height, rect.height);
|
||||
|
||||
if (selectedNode != null && selectedNode.Tracking == node)
|
||||
// Being tracked by current selection mark
|
||||
if (selectedNode != null && selectedNode.Tracking == node)
|
||||
{
|
||||
GUI.DrawTexture(indicatorRect, Styles.TrackingBranchIcon);
|
||||
}
|
||||
else if (node.Active)
|
||||
// Active branch mark
|
||||
else if (node.Active)
|
||||
{
|
||||
GUI.DrawTexture(indicatorRect, Styles.ActiveBranchIcon);
|
||||
}
|
||||
else if (node.Tracking != null)
|
||||
// Tracking mark
|
||||
else if (node.Tracking != null)
|
||||
{
|
||||
GUI.DrawTexture(indicatorRect, Styles.TrackingBranchIcon);
|
||||
}
|
||||
|
@ -573,10 +533,14 @@ namespace GitHub.Unity
|
|||
|
||||
// Children
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.Space(Styles.TreeIndentation);
|
||||
GUILayout.BeginVertical();
|
||||
{
|
||||
OnTreeNodeChildrenGUI(node);
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// Click selection of the node as well as branch switch
|
||||
|
@ -587,32 +551,29 @@ namespace GitHub.Unity
|
|||
|
||||
if (Event.current.clickCount > 1 && mode == BranchesMode.Default)
|
||||
{
|
||||
if (node.Type == NodeType.LocalBranch && EditorUtility.DisplayDialog(
|
||||
ConfirmSwitchTitle,
|
||||
string.Format(ConfirmSwitchMessage, node.Name),
|
||||
ConfirmSwitchOK,
|
||||
ConfirmSwitchCancel
|
||||
))
|
||||
if (node.Type == NodeType.LocalBranch &&
|
||||
EditorUtility.DisplayDialog(ConfirmSwitchTitle, String.Format(ConfirmSwitchMessage, node.Name), ConfirmSwitchOK,
|
||||
ConfirmSwitchCancel))
|
||||
{
|
||||
GitSwitchBranchesTask.Schedule(node.Name, Refresh);
|
||||
}
|
||||
else if (node.Type == NodeType.RemoteBranch)
|
||||
{
|
||||
GitBranchCreateTask.Schedule(selectedNode.Name.Substring(selectedNode.Name.IndexOf('/') + 1), selectedNode.Name, Refresh);
|
||||
GitBranchCreateTask.Schedule(selectedNode.Name.Substring(selectedNode.Name.IndexOf('/') + 1), selectedNode.Name,
|
||||
Refresh);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OnTreeNodeChildrenGUI(BranchTreeNode node)
|
||||
private void OnTreeNodeChildrenGUI(BranchTreeNode node)
|
||||
{
|
||||
if (node == null || node.Children == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int index = 0; index < node.Children.Count; ++index)
|
||||
for (var index = 0; index < node.Children.Count; ++index)
|
||||
{
|
||||
// The actual GUI of the child
|
||||
OnTreeNodeGUI(node.Children[index]);
|
||||
|
@ -620,22 +581,8 @@ namespace GitHub.Unity
|
|||
// Keyboard navigation if this child is the current selection
|
||||
if (selectedNode == node.Children[index] && GUIUtility.keyboardControl == listID && Event.current.type == EventType.KeyDown)
|
||||
{
|
||||
int directionY =
|
||||
Event.current.keyCode == KeyCode.UpArrow ?
|
||||
-1
|
||||
:
|
||||
Event.current.keyCode == KeyCode.DownArrow ?
|
||||
1
|
||||
:
|
||||
0,
|
||||
directionX =
|
||||
Event.current.keyCode == KeyCode.LeftArrow ?
|
||||
-1
|
||||
:
|
||||
Event.current.keyCode == KeyCode.RightArrow ?
|
||||
1
|
||||
:
|
||||
0;
|
||||
int directionY = Event.current.keyCode == KeyCode.UpArrow ? -1 : Event.current.keyCode == KeyCode.DownArrow ? 1 : 0,
|
||||
directionX = Event.current.keyCode == KeyCode.LeftArrow ? -1 : Event.current.keyCode == KeyCode.RightArrow ? 1 : 0;
|
||||
|
||||
if (directionY < 0 && index > 0)
|
||||
{
|
||||
|
@ -647,12 +594,12 @@ namespace GitHub.Unity
|
|||
newNodeSelection = node.Children[index + 1];
|
||||
Event.current.Use();
|
||||
}
|
||||
else if(directionX < 0)
|
||||
else if (directionX < 0)
|
||||
{
|
||||
newNodeSelection = node;
|
||||
Event.current.Use();
|
||||
}
|
||||
else if(directionX > 0 && node.Children[index].Children.Count > 0)
|
||||
else if (directionX > 0 && node.Children[index].Children.Count > 0)
|
||||
{
|
||||
newNodeSelection = node.Children[index].Children[0];
|
||||
Event.current.Use();
|
||||
|
@ -660,5 +607,50 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum NodeType
|
||||
{
|
||||
Folder,
|
||||
LocalBranch,
|
||||
RemoteBranch
|
||||
}
|
||||
|
||||
private enum BranchesMode
|
||||
{
|
||||
Default,
|
||||
Create
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class BranchTreeNode
|
||||
{
|
||||
private readonly List<BranchTreeNode> children = new List<BranchTreeNode>();
|
||||
|
||||
public string Label;
|
||||
public BranchTreeNode Tracking;
|
||||
|
||||
public BranchTreeNode(string name, NodeType type, bool active)
|
||||
{
|
||||
Label = Name = name;
|
||||
Type = type;
|
||||
Active = active;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public NodeType Type { get; }
|
||||
public bool Active { get; }
|
||||
|
||||
public IList<BranchTreeNode> Children
|
||||
{
|
||||
get { return children; }
|
||||
}
|
||||
}
|
||||
|
||||
private struct Remote
|
||||
{
|
||||
// TODO: Pull in and store more data from GitListRemotesTask
|
||||
public string Name;
|
||||
public BranchTreeNode Root;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,37 +1,100 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
[System.Serializable]
|
||||
[Serializable]
|
||||
class ChangesView : Subview
|
||||
{
|
||||
const string
|
||||
SummaryLabel = "Commit summary",
|
||||
DescriptionLabel = "Commit description",
|
||||
CommitButton = "Commit to <b>{0}</b>",
|
||||
SelectAllButton = "All",
|
||||
SelectNoneButton = "None",
|
||||
ChangedFilesLabel = "{0} changed files",
|
||||
OneChangedFileLabel = "1 changed file",
|
||||
NoChangedFilesLabel = "No changed files";
|
||||
private const string SummaryLabel = "Commit summary";
|
||||
private const string DescriptionLabel = "Commit description";
|
||||
private const string CommitButton = "Commit to <b>{0}</b>";
|
||||
private const string SelectAllButton = "All";
|
||||
private const string SelectNoneButton = "None";
|
||||
private const string ChangedFilesLabel = "{0} changed files";
|
||||
private const string OneChangedFileLabel = "1 changed file";
|
||||
private const string NoChangedFilesLabel = "No changed files";
|
||||
|
||||
[NonSerialized] private bool lockCommit = true;
|
||||
[SerializeField] private string commitBody = "";
|
||||
|
||||
[SerializeField] Vector2
|
||||
verticalScroll,
|
||||
horizontalScroll;
|
||||
[SerializeField] string
|
||||
commitMessage = "",
|
||||
commitBody = "",
|
||||
currentBranch = "[unknown]";
|
||||
[SerializeField] ChangesetTreeView tree = new ChangesetTreeView();
|
||||
[SerializeField] private string commitMessage = "";
|
||||
[SerializeField] private string currentBranch = "[unknown]";
|
||||
[SerializeField] private Vector2 horizontalScroll;
|
||||
[SerializeField] private ChangesetTreeView tree = new ChangesetTreeView();
|
||||
[SerializeField] private Vector2 verticalScroll;
|
||||
|
||||
public override void Refresh()
|
||||
{
|
||||
GitStatusTask.Schedule();
|
||||
}
|
||||
|
||||
bool lockCommit = true;
|
||||
public override void OnGUI()
|
||||
{
|
||||
var scroll = verticalScroll;
|
||||
scroll = GUILayout.BeginScrollView(verticalScroll);
|
||||
{
|
||||
if (tree.Height > 0)
|
||||
{
|
||||
verticalScroll = scroll;
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
EditorGUI.BeginDisabledGroup(tree.Entries.Count == 0);
|
||||
{
|
||||
if (GUILayout.Button(SelectAllButton, EditorStyles.miniButtonLeft))
|
||||
{
|
||||
SelectAll();
|
||||
}
|
||||
|
||||
if (GUILayout.Button(SelectNoneButton, EditorStyles.miniButtonRight))
|
||||
{
|
||||
SelectNone();
|
||||
}
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
GUILayout.Label(
|
||||
tree.Entries.Count == 0
|
||||
? NoChangedFilesLabel
|
||||
: tree.Entries.Count == 1 ? OneChangedFileLabel : String.Format(ChangedFilesLabel, tree.Entries.Count),
|
||||
EditorStyles.miniLabel);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginVertical(Styles.CommitFileAreaStyle);
|
||||
{
|
||||
// Specify a minimum height if we can - avoiding vertical scrollbars on both the outer and inner scroll view
|
||||
if (tree.Height > 0)
|
||||
{
|
||||
horizontalScroll = GUILayout.BeginScrollView(horizontalScroll, GUILayout.MinHeight(tree.Height),
|
||||
GUILayout.MaxHeight(100000f)
|
||||
// NOTE: This ugliness is necessary as unbounded MaxHeight appears impossible when MinHeight is specified
|
||||
);
|
||||
}
|
||||
|
||||
else // if we have no minimum height to work with, just stretch and hope
|
||||
{
|
||||
horizontalScroll = GUILayout.BeginScrollView(horizontalScroll);
|
||||
}
|
||||
|
||||
{// scroll view block started above
|
||||
tree.OnGUI();
|
||||
}
|
||||
GUILayout.EndScrollView();
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
|
||||
// Do the commit details area
|
||||
OnCommitDetailsAreaGUI();
|
||||
}
|
||||
GUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
protected override void OnShow()
|
||||
{
|
||||
|
@ -39,20 +102,12 @@ namespace GitHub.Unity
|
|||
GitStatusTask.RegisterCallback(OnStatusUpdate);
|
||||
}
|
||||
|
||||
|
||||
protected override void OnHide()
|
||||
{
|
||||
GitStatusTask.UnregisterCallback(OnStatusUpdate);
|
||||
}
|
||||
|
||||
|
||||
public override void Refresh()
|
||||
{
|
||||
GitStatusTask.Schedule();
|
||||
}
|
||||
|
||||
|
||||
void OnStatusUpdate(GitStatus update)
|
||||
private void OnStatusUpdate(GitStatus update)
|
||||
{
|
||||
// Set branch state
|
||||
currentBranch = update.LocalBranch;
|
||||
|
@ -63,70 +118,14 @@ namespace GitHub.Unity
|
|||
lockCommit = false;
|
||||
}
|
||||
|
||||
|
||||
public override void OnGUI()
|
||||
private void OnCommitDetailsAreaGUI()
|
||||
{
|
||||
if (tree.Height > 0)
|
||||
{
|
||||
verticalScroll = GUILayout.BeginScrollView(verticalScroll);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.BeginScrollView(verticalScroll);
|
||||
}
|
||||
GUILayout.BeginHorizontal();
|
||||
EditorGUI.BeginDisabledGroup(tree.Entries.Count == 0);
|
||||
if (GUILayout.Button(SelectAllButton, EditorStyles.miniButtonLeft))
|
||||
{
|
||||
SelectAll();
|
||||
}
|
||||
|
||||
if (GUILayout.Button(SelectNoneButton, EditorStyles.miniButtonRight))
|
||||
{
|
||||
SelectNone();
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
GUILayout.Label(
|
||||
tree.Entries.Count == 0 ? NoChangedFilesLabel :
|
||||
tree.Entries.Count == 1 ? OneChangedFileLabel :
|
||||
string.Format(ChangedFilesLabel, tree.Entries.Count),
|
||||
EditorStyles.miniLabel
|
||||
);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginVertical(Styles.CommitFileAreaStyle);
|
||||
if (tree.Height > 0)
|
||||
// Specify a minimum height if we can - avoiding vertical scrollbars on both the outer and inner scroll view
|
||||
{
|
||||
horizontalScroll = GUILayout.BeginScrollView(
|
||||
horizontalScroll,
|
||||
GUILayout.MinHeight(tree.Height),
|
||||
GUILayout.MaxHeight(100000f) // NOTE: This ugliness is necessary as unbounded MaxHeight appears impossible when MinHeight is specified
|
||||
);
|
||||
}
|
||||
// If we have no minimum height to work with, just stretch and hope
|
||||
else
|
||||
{
|
||||
horizontalScroll = GUILayout.BeginScrollView(horizontalScroll);
|
||||
}
|
||||
tree.OnGUI();
|
||||
GUILayout.EndScrollView();
|
||||
GUILayout.EndVertical();
|
||||
|
||||
// Do the commit details area
|
||||
OnCommitDetailsAreaGUI();
|
||||
GUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
|
||||
void OnCommitDetailsAreaGUI()
|
||||
{
|
||||
GUILayout.BeginVertical(
|
||||
GUILayout.Height(Mathf.Clamp(position.height * Styles.CommitAreaDefaultRatio, Styles.CommitAreaMinHeight, Styles.CommitAreaMaxHeight))
|
||||
GUILayout.BeginVertical(GUILayout.Height(
|
||||
Mathf.Clamp(position.height * Styles.CommitAreaDefaultRatio,
|
||||
Styles.CommitAreaMinHeight,
|
||||
Styles.CommitAreaMaxHeight))
|
||||
);
|
||||
{
|
||||
GUILayout.Label(SummaryLabel);
|
||||
commitMessage = GUILayout.TextField(commitMessage);
|
||||
|
||||
|
@ -135,37 +134,39 @@ namespace GitHub.Unity
|
|||
|
||||
// Disable committing when already committing or if we don't have all the data needed
|
||||
EditorGUI.BeginDisabledGroup(lockCommit || string.IsNullOrEmpty(commitMessage) || !tree.CommitTargets.Any(t => t.Any));
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button(string.Format(CommitButton, currentBranch), Styles.CommitButtonStyle))
|
||||
if (GUILayout.Button(String.Format(CommitButton, currentBranch), Styles.CommitButtonStyle))
|
||||
{
|
||||
Commit();
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
|
||||
void SelectAll()
|
||||
private void SelectAll()
|
||||
{
|
||||
for (int index = 0; index < tree.CommitTargets.Count; ++index)
|
||||
for (var index = 0; index < tree.CommitTargets.Count; ++index)
|
||||
{
|
||||
tree.CommitTargets[index].All = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SelectNone()
|
||||
private void SelectNone()
|
||||
{
|
||||
for (int index = 0; index < tree.CommitTargets.Count; ++index)
|
||||
for (var index = 0; index < tree.CommitTargets.Count; ++index)
|
||||
{
|
||||
tree.CommitTargets[index].All = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Commit()
|
||||
private void Commit()
|
||||
{
|
||||
// Do not allow new commits before we have received one successful update
|
||||
lockCommit = true;
|
||||
|
@ -175,11 +176,10 @@ namespace GitHub.Unity
|
|||
Enumerable.Range(0, tree.Entries.Count).Where(i => tree.CommitTargets[i].All).Select(i => tree.Entries[i].Path),
|
||||
commitMessage,
|
||||
commitBody,
|
||||
() =>
|
||||
{
|
||||
() => {
|
||||
commitMessage = "";
|
||||
commitBody = "";
|
||||
for (int index = 0; index < tree.Entries.Count; ++index)
|
||||
for (var index = 0; index < tree.Entries.Count; ++index)
|
||||
{
|
||||
tree.CommitTargets[index].Clear();
|
||||
}
|
||||
|
|
|
@ -1,19 +1,24 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
[Serializable]
|
||||
class GitCommitTarget
|
||||
{
|
||||
public bool All = false;
|
||||
// TODO: Add line tracking here
|
||||
[SerializeField] public bool All = false;
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
All = false;
|
||||
// TODO: Add line tracking here
|
||||
}
|
||||
|
||||
// TODO: Add line tracking here
|
||||
|
||||
public bool Any
|
||||
{
|
||||
|
@ -22,39 +27,354 @@ namespace GitHub.Unity
|
|||
return All; // TODO: Add line tracking here
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
All = false;
|
||||
// TODO: Add line tracking here
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[System.Serializable]
|
||||
[Serializable]
|
||||
class ChangesetTreeView : Subview
|
||||
{
|
||||
enum CommitState
|
||||
private const string BasePathLabel = "{0}";
|
||||
private const string NoChangesLabel = "No changes found";
|
||||
|
||||
[SerializeField] private List<GitStatusEntry> entries = new List<GitStatusEntry>();
|
||||
[SerializeField] private List<GitCommitTarget> entryCommitTargets = new List<GitCommitTarget>();
|
||||
[SerializeField] private List<string> foldedTreeEntries = new List<string>();
|
||||
[SerializeField] private FileTreeNode tree;
|
||||
|
||||
public void Update(IList<GitStatusEntry> newEntries)
|
||||
{
|
||||
// Handle the empty list scenario
|
||||
if (!newEntries.Any())
|
||||
{
|
||||
entries.Clear();
|
||||
entryCommitTargets.Clear();
|
||||
tree = null;
|
||||
foldedTreeEntries.Clear();
|
||||
|
||||
OnCommitTreeChange();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove what got nuked
|
||||
for (var index = 0; index < entries.Count;)
|
||||
{
|
||||
if (!newEntries.Contains(entries[index]))
|
||||
{
|
||||
entries.RemoveAt(index);
|
||||
entryCommitTargets.RemoveAt(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove folding state of nuked items
|
||||
for (var index = 0; index < foldedTreeEntries.Count;)
|
||||
{
|
||||
if (!newEntries.Any(e => e.Path.IndexOf(foldedTreeEntries[index]) == 0))
|
||||
{
|
||||
foldedTreeEntries.RemoveAt(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add new stuff
|
||||
for (var index = 0; index < newEntries.Count; ++index)
|
||||
{
|
||||
var entry = newEntries[index];
|
||||
if (!entries.Contains(entry))
|
||||
{
|
||||
entries.Add(entry);
|
||||
entryCommitTargets.Add(new GitCommitTarget());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Filter .meta files - consider adding them as children of the asset or folder they're supporting
|
||||
|
||||
// TODO: In stead of completely rebuilding the tree structure, figure out a way to migrate open/closed states from the old tree to the new
|
||||
|
||||
// Build tree structure
|
||||
tree = new FileTreeNode(Utility.FindCommonPath("" + Path.DirectorySeparatorChar, entries.Select(e => e.Path)));
|
||||
tree.RepositoryPath = tree.Path;
|
||||
for (var index = 0; index < entries.Count; index++)
|
||||
{
|
||||
var node = new FileTreeNode(entries[index].Path.Substring(tree.Path.Length)) { Target = entryCommitTargets[index] };
|
||||
if (!string.IsNullOrEmpty(entries[index].ProjectPath))
|
||||
{
|
||||
node.Icon = AssetDatabase.GetCachedIcon(entries[index].ProjectPath);
|
||||
}
|
||||
|
||||
BuildTree(tree, node);
|
||||
}
|
||||
|
||||
OnCommitTreeChange();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
GUILayout.BeginVertical();
|
||||
{
|
||||
// The file tree (when available)
|
||||
if (tree != null && entries.Any())
|
||||
{
|
||||
// Base path label
|
||||
if (!string.IsNullOrEmpty(tree.Path))
|
||||
{
|
||||
GUILayout.Label(String.Format(BasePathLabel, tree.Path));
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.Space(Styles.TreeIndentation + Styles.TreeRootIndentation);
|
||||
GUILayout.BeginVertical();
|
||||
{
|
||||
// Root nodes
|
||||
foreach (var node in tree.Children)
|
||||
{
|
||||
TreeNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// If we have no minimum height calculated, do that now and repaint so it can be used
|
||||
if (Height == 0f && Event.current.type == EventType.Repaint)
|
||||
{
|
||||
Height = GUILayoutUtility.GetLastRect().yMax + Styles.MinCommitTreePadding;
|
||||
Repaint();
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.Label(NoChangesLabel);
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void OnCommitTreeChange()
|
||||
{
|
||||
Height = 0f;
|
||||
Repaint();
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private void BuildTree(FileTreeNode parent, FileTreeNode node)
|
||||
{
|
||||
if (string.IsNullOrEmpty(node.Label))
|
||||
{
|
||||
// TODO: We should probably reassign this target onto the parent? Depends on how we want to handle .meta files for folders
|
||||
return;
|
||||
}
|
||||
|
||||
node.RepositoryPath = Path.Combine(parent.RepositoryPath, node.Label);
|
||||
parent.Open = !foldedTreeEntries.Contains(parent.RepositoryPath);
|
||||
|
||||
// Is this node inside a folder?
|
||||
var index = node.Label.IndexOf(Path.DirectorySeparatorChar);
|
||||
if (index > 0)
|
||||
{
|
||||
// Figure out what the root folder is and chop it from the path
|
||||
var root = node.Label.Substring(0, index);
|
||||
node.Label = node.Label.Substring(index + 1);
|
||||
|
||||
// Look for a branch matching our root in the existing children
|
||||
var found = false;
|
||||
foreach (var child in parent.Children)
|
||||
{
|
||||
// If we found the branch, continue building from that branch
|
||||
if (child.Label.Equals(root))
|
||||
{
|
||||
found = true;
|
||||
BuildTree(child, node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// No existing branch - we will have to add a new one to build from
|
||||
if (!found)
|
||||
{
|
||||
BuildTree(parent.Add(new FileTreeNode(root) { RepositoryPath = Path.Combine(parent.RepositoryPath, root) }), node);
|
||||
}
|
||||
}
|
||||
// Not inside a folder - just add this node right here
|
||||
else
|
||||
{
|
||||
parent.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
private void TreeNode(FileTreeNode node)
|
||||
{
|
||||
var target = node.Target;
|
||||
var isFolder = node.Children.Any();
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
if (!Readonly)
|
||||
{
|
||||
// Commit inclusion toggle
|
||||
var state = node.State;
|
||||
var toggled = state == CommitState.All;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
{
|
||||
toggled = GUILayout.Toggle(toggled, "", state == CommitState.Some ? Styles.ToggleMixedStyle : GUI.skin.toggle,
|
||||
GUILayout.ExpandWidth(false));
|
||||
}
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
node.State = toggled ? CommitState.All : CommitState.None;
|
||||
}
|
||||
}
|
||||
|
||||
// Foldout
|
||||
if (isFolder)
|
||||
{
|
||||
Rect foldoutRect;
|
||||
|
||||
if (Readonly)
|
||||
{
|
||||
foldoutRect = GUILayoutUtility.GetRect(1, 1);
|
||||
foldoutRect.Set(foldoutRect.x - 5f, foldoutRect.y, 0f, EditorGUIUtility.singleLineHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
foldoutRect = GUILayoutUtility.GetLastRect();
|
||||
}
|
||||
|
||||
foldoutRect.Set(foldoutRect.x - Styles.FoldoutWidth + Styles.FoldoutIndentation, foldoutRect.y, Styles.FoldoutWidth,
|
||||
foldoutRect.height);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
{
|
||||
node.Open = GUI.Toggle(foldoutRect, node.Open, "", EditorStyles.foldout);
|
||||
}
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (!node.Open && !foldedTreeEntries.Contains(node.RepositoryPath))
|
||||
{
|
||||
foldedTreeEntries.Add(node.RepositoryPath);
|
||||
}
|
||||
else if (node.Open)
|
||||
{
|
||||
foldedTreeEntries.Remove(node.RepositoryPath);
|
||||
}
|
||||
|
||||
OnCommitTreeChange();
|
||||
}
|
||||
}
|
||||
|
||||
// Node icon and label
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.Space(Styles.CommitIconHorizontalPadding);
|
||||
var iconRect = GUILayoutUtility.GetRect(Styles.CommitIconSize, Styles.CommitIconSize, GUILayout.ExpandWidth(false));
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
GUI.DrawTexture(iconRect, node.Icon ?? (isFolder ? Styles.FolderIcon : Styles.DefaultAssetIcon),
|
||||
ScaleMode.ScaleToFit);
|
||||
}
|
||||
GUILayout.Space(Styles.CommitIconHorizontalPadding);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.Label(new GUIContent(node.Label, node.RepositoryPath), GUILayout.ExpandWidth(true));
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
// Current status (if any)
|
||||
if (target != null)
|
||||
{
|
||||
var status = entries[entryCommitTargets.IndexOf(target)].Status;
|
||||
var statusIcon = Styles.GetGitFileStatusIcon(status);
|
||||
GUILayout.Label(statusIcon != null ? new GUIContent(statusIcon) : new GUIContent(status.ToString()),
|
||||
GUILayout.ExpandWidth(false), GUILayout.MaxHeight(EditorGUIUtility.singleLineHeight));
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
// Render children (if any and folded out)
|
||||
if (isFolder && node.Open)
|
||||
{
|
||||
GUILayout.Space(Styles.TreeIndentation);
|
||||
GUILayout.BeginVertical();
|
||||
{
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
TreeNode(child);
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public float Height { get; protected set; }
|
||||
public bool Readonly { get; set; }
|
||||
|
||||
public IList<GitStatusEntry> Entries
|
||||
{
|
||||
get { return entries; }
|
||||
}
|
||||
|
||||
public IList<GitCommitTarget> CommitTargets
|
||||
{
|
||||
get { return entryCommitTargets; }
|
||||
}
|
||||
|
||||
private enum CommitState
|
||||
{
|
||||
None,
|
||||
Some,
|
||||
All
|
||||
}
|
||||
|
||||
|
||||
[Serializable]
|
||||
class FileTreeNode
|
||||
private class FileTreeNode
|
||||
{
|
||||
public string Label, RepositoryPath;
|
||||
public bool Open = true;
|
||||
public GitCommitTarget Target;
|
||||
public Texture Icon;
|
||||
[SerializeField] private List<FileTreeNode> children = new List<FileTreeNode>();
|
||||
|
||||
[SerializeField] public Texture Icon;
|
||||
[SerializeField] public string Label;
|
||||
[SerializeField] public bool Open = true;
|
||||
[SerializeField] private string path;
|
||||
[SerializeField] public string RepositoryPath;
|
||||
[SerializeField] public GitCommitTarget Target;
|
||||
|
||||
[SerializeField] string path;
|
||||
[SerializeField] List<FileTreeNode> children = new List<FileTreeNode>();
|
||||
public FileTreeNode(string path)
|
||||
{
|
||||
this.path = path;
|
||||
Label = path;
|
||||
}
|
||||
|
||||
public FileTreeNode Add(FileTreeNode child)
|
||||
{
|
||||
children.Add(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
public CommitState State
|
||||
{
|
||||
|
@ -64,22 +384,20 @@ namespace GitHub.Unity
|
|||
{
|
||||
return Target.All ? CommitState.All : Target.Any ? CommitState.Some : CommitState.None;
|
||||
}
|
||||
else
|
||||
|
||||
var allCount = children.Count(c => c.State == CommitState.All);
|
||||
|
||||
if (allCount == children.Count)
|
||||
{
|
||||
int allCount = children.Count(c => c.State == CommitState.All);
|
||||
|
||||
if (allCount == children.Count)
|
||||
{
|
||||
return CommitState.All;
|
||||
}
|
||||
|
||||
if (allCount > 0)
|
||||
{
|
||||
return CommitState.Some;
|
||||
}
|
||||
|
||||
return children.Count(c => c.State == CommitState.Some) > 0 ? CommitState.Some : CommitState.None;
|
||||
return CommitState.All;
|
||||
}
|
||||
|
||||
if (allCount > 0)
|
||||
{
|
||||
return CommitState.Some;
|
||||
}
|
||||
|
||||
return children.Count(c => c.State == CommitState.Some) > 0 ? CommitState.Some : CommitState.None;
|
||||
}
|
||||
set
|
||||
{
|
||||
|
@ -101,7 +419,7 @@ namespace GitHub.Unity
|
|||
}
|
||||
else
|
||||
{
|
||||
for (int index = 0; index < children.Count; ++index)
|
||||
for (var index = 0; index < children.Count; ++index)
|
||||
{
|
||||
children[index].State = value;
|
||||
}
|
||||
|
@ -109,329 +427,15 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public FileTreeNode(string path)
|
||||
public string Path
|
||||
{
|
||||
this.path = path;
|
||||
Label = path;
|
||||
get { return path; }
|
||||
}
|
||||
|
||||
|
||||
public string Path { get { return path; } }
|
||||
public IEnumerable<FileTreeNode> Children { get { return children; } }
|
||||
|
||||
|
||||
public FileTreeNode Add(FileTreeNode child)
|
||||
public IEnumerable<FileTreeNode> Children
|
||||
{
|
||||
children.Add(child);
|
||||
|
||||
return child;
|
||||
get { return children; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const string
|
||||
BasePathLabel = "{0}",
|
||||
NoChangesLabel = "No changes found";
|
||||
|
||||
|
||||
[SerializeField] List<GitStatusEntry> entries = new List<GitStatusEntry>();
|
||||
[SerializeField] List<GitCommitTarget> entryCommitTargets = new List<GitCommitTarget>();
|
||||
[SerializeField] FileTreeNode tree;
|
||||
[SerializeField] List<string> foldedTreeEntries = new List<string>();
|
||||
|
||||
|
||||
public float Height { get; protected set; }
|
||||
public bool Readonly { get; set; }
|
||||
|
||||
|
||||
public IList<GitStatusEntry> Entries
|
||||
{
|
||||
get
|
||||
{
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public IList<GitCommitTarget> CommitTargets
|
||||
{
|
||||
get
|
||||
{
|
||||
return entryCommitTargets;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Update(IList<GitStatusEntry> newEntries)
|
||||
{
|
||||
// Handle the empty list scenario
|
||||
if (!newEntries.Any())
|
||||
{
|
||||
entries.Clear();
|
||||
entryCommitTargets.Clear();
|
||||
tree = null;
|
||||
foldedTreeEntries.Clear();
|
||||
|
||||
OnCommitTreeChange();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove what got nuked
|
||||
for (int index = 0; index < entries.Count;)
|
||||
{
|
||||
if (!newEntries.Contains(entries[index]))
|
||||
{
|
||||
entries.RemoveAt(index);
|
||||
entryCommitTargets.RemoveAt(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove folding state of nuked items
|
||||
for (int index = 0; index < foldedTreeEntries.Count;)
|
||||
{
|
||||
if (!newEntries.Any(e => e.Path.IndexOf(foldedTreeEntries[index]) == 0))
|
||||
{
|
||||
foldedTreeEntries.RemoveAt(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
// Add new stuff
|
||||
for (int index = 0; index < newEntries.Count; ++index)
|
||||
{
|
||||
GitStatusEntry entry = newEntries[index];
|
||||
if (!entries.Contains(entry))
|
||||
{
|
||||
entries.Add(entry);
|
||||
entryCommitTargets.Add(new GitCommitTarget());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Filter .meta files - consider adding them as children of the asset or folder they're supporting
|
||||
|
||||
// TODO: In stead of completely rebuilding the tree structure, figure out a way to migrate open/closed states from the old tree to the new
|
||||
|
||||
// Build tree structure
|
||||
tree = new FileTreeNode(Utility.FindCommonPath("" + Path.DirectorySeparatorChar, entries.Select(e => e.Path)));
|
||||
tree.RepositoryPath = tree.Path;
|
||||
for (int index = 0; index < entries.Count; ++index)
|
||||
{
|
||||
FileTreeNode node = new FileTreeNode(entries[index].Path.Substring(tree.Path.Length)){ Target = entryCommitTargets[index] };
|
||||
if (!string.IsNullOrEmpty(entries[index].ProjectPath))
|
||||
{
|
||||
node.Icon = AssetDatabase.GetCachedIcon(entries[index].ProjectPath);
|
||||
}
|
||||
|
||||
BuildTree(tree, node);
|
||||
}
|
||||
|
||||
OnCommitTreeChange();
|
||||
}
|
||||
|
||||
|
||||
void OnCommitTreeChange()
|
||||
{
|
||||
Height = 0f;
|
||||
Repaint();
|
||||
Repaint();
|
||||
}
|
||||
|
||||
|
||||
void BuildTree(FileTreeNode parent, FileTreeNode node)
|
||||
{
|
||||
if (string.IsNullOrEmpty(node.Label))
|
||||
{
|
||||
// TODO: We should probably reassign this target onto the parent? Depends on how we want to handle .meta files for folders
|
||||
return;
|
||||
}
|
||||
|
||||
node.RepositoryPath = Path.Combine(parent.RepositoryPath, node.Label);
|
||||
parent.Open = !foldedTreeEntries.Contains(parent.RepositoryPath);
|
||||
|
||||
// Is this node inside a folder?
|
||||
int index = node.Label.IndexOf(Path.DirectorySeparatorChar);
|
||||
if (index > 0)
|
||||
{
|
||||
// Figure out what the root folder is and chop it from the path
|
||||
string root = node.Label.Substring(0, index);
|
||||
node.Label = node.Label.Substring(index + 1);
|
||||
|
||||
// Look for a branch matching our root in the existing children
|
||||
bool found = false;
|
||||
foreach (FileTreeNode child in parent.Children)
|
||||
{
|
||||
if (child.Label.Equals(root))
|
||||
// If we found the branch, continue building from that branch
|
||||
{
|
||||
found = true;
|
||||
BuildTree(child, node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// No existing branch - we will have to add a new one to build from
|
||||
if (!found)
|
||||
{
|
||||
BuildTree(parent.Add(new FileTreeNode(root){ RepositoryPath = Path.Combine(parent.RepositoryPath, root) }), node);
|
||||
}
|
||||
}
|
||||
// Not inside a folder - just add this node right here
|
||||
else
|
||||
{
|
||||
parent.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
GUILayout.BeginVertical();
|
||||
// The file tree (when available)
|
||||
if (tree != null && entries.Any())
|
||||
{
|
||||
// Base path label
|
||||
if (!string.IsNullOrEmpty(tree.Path))
|
||||
{
|
||||
GUILayout.Label(string.Format(BasePathLabel, tree.Path));
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Space(Styles.TreeIndentation + Styles.TreeRootIndentation);
|
||||
GUILayout.BeginVertical();
|
||||
// Root nodes
|
||||
foreach (FileTreeNode node in tree.Children)
|
||||
{
|
||||
TreeNode(node);
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (Height == 0f && Event.current.type == EventType.Repaint)
|
||||
// If we have no minimum height calculated, do that now and repaint so it can be used
|
||||
{
|
||||
Height = GUILayoutUtility.GetLastRect().yMax + Styles.MinCommitTreePadding;
|
||||
Repaint();
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.Label(NoChangesLabel);
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
|
||||
void TreeNode(FileTreeNode node)
|
||||
{
|
||||
GitCommitTarget target = node.Target;
|
||||
bool isFolder = node.Children.Any();
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
if (!Readonly)
|
||||
{
|
||||
// Commit inclusion toggle
|
||||
CommitState state = node.State;
|
||||
bool toggled = state == CommitState.All;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
toggled = GUILayout.Toggle(toggled, "", state == CommitState.Some ? Styles.ToggleMixedStyle : GUI.skin.toggle, GUILayout.ExpandWidth(false));
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
node.State = toggled ? CommitState.All : CommitState.None;
|
||||
}
|
||||
}
|
||||
|
||||
// Foldout
|
||||
if (isFolder)
|
||||
{
|
||||
Rect foldoutRect;
|
||||
|
||||
if (Readonly)
|
||||
{
|
||||
foldoutRect = GUILayoutUtility.GetRect(1, 1);
|
||||
foldoutRect.Set(foldoutRect.x - 5f, foldoutRect.y, 0f, EditorGUIUtility.singleLineHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
foldoutRect = GUILayoutUtility.GetLastRect();
|
||||
}
|
||||
foldoutRect.Set(foldoutRect.x - Styles.FoldoutWidth + Styles.FoldoutIndentation, foldoutRect.y, Styles.FoldoutWidth, foldoutRect.height);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
node.Open = GUI.Toggle(foldoutRect, node.Open, "", EditorStyles.foldout);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (!node.Open && !foldedTreeEntries.Contains(node.RepositoryPath))
|
||||
{
|
||||
foldedTreeEntries.Add(node.RepositoryPath);
|
||||
}
|
||||
else if (node.Open)
|
||||
{
|
||||
foldedTreeEntries.Remove(node.RepositoryPath);
|
||||
}
|
||||
|
||||
OnCommitTreeChange();
|
||||
}
|
||||
}
|
||||
|
||||
// Node icon and label
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Space(Styles.CommitIconHorizontalPadding);
|
||||
Rect iconRect = GUILayoutUtility.GetRect(Styles.CommitIconSize, Styles.CommitIconSize, GUILayout.ExpandWidth(false));
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
GUI.DrawTexture(iconRect, node.Icon ?? (isFolder ? Styles.FolderIcon : Styles.DefaultAssetIcon), ScaleMode.ScaleToFit);
|
||||
}
|
||||
GUILayout.Space(Styles.CommitIconHorizontalPadding);
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.Label(new GUIContent(node.Label, node.RepositoryPath), GUILayout.ExpandWidth(true));
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
// Current status (if any)
|
||||
if (target != null)
|
||||
{
|
||||
GitFileStatus status = entries[entryCommitTargets.IndexOf(target)].Status;
|
||||
Texture2D statusIcon = Styles.GetGitFileStatusIcon(status);
|
||||
GUILayout.Label(
|
||||
statusIcon != null ? new GUIContent(statusIcon) : new GUIContent(status.ToString()),
|
||||
GUILayout.ExpandWidth(false),
|
||||
GUILayout.MaxHeight(EditorGUIUtility.singleLineHeight)
|
||||
);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
// Render children (if any and folded out)
|
||||
if (isFolder && node.Open)
|
||||
{
|
||||
GUILayout.Space(Styles.TreeIndentation);
|
||||
GUILayout.BeginVertical();
|
||||
foreach (FileTreeNode child in node.Children)
|
||||
{
|
||||
TreeNode(child);
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,107 +1,55 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
[System.Serializable]
|
||||
[Serializable]
|
||||
class HistoryView : Subview
|
||||
{
|
||||
enum LogEntryState
|
||||
{
|
||||
Normal,
|
||||
Local,
|
||||
Remote
|
||||
}
|
||||
private const string HistoryFocusAll = "(All)";
|
||||
private const string HistoryFocusSingle = "Focus: <b>{0}</b>";
|
||||
private const string PullButton = "Pull";
|
||||
private const string PullButtonCount = "Pull (<b>{0}</b>)";
|
||||
private const string PushButton = "Push";
|
||||
private const string PushButtonCount = "Push (<b>{0}</b>)";
|
||||
private const string PullConfirmTitle = "Pull Changes?";
|
||||
private const string PullConfirmDescription = "Would you like to pull changes from remote '{0}'?";
|
||||
private const string PullConfirmYes = "Pull";
|
||||
private const string PullConfirmCancel = "Cancel";
|
||||
private const string PushConfirmTitle = "Push Changes?";
|
||||
private const string PushConfirmDescription = "Would you like to push changes to remote '{0}'?";
|
||||
private const string PushConfirmYes = "Push";
|
||||
private const string PushConfirmCancel = "Cancel";
|
||||
private const string ClearSelectionButton = "x";
|
||||
private const int HistoryExtraItemCount = 10;
|
||||
private const float MaxChangelistHeightRatio = .2f;
|
||||
|
||||
[NonSerialized] private string currentRemote = "placeholder";
|
||||
[NonSerialized] private int historyStartIndex;
|
||||
[NonSerialized] private int historyStopIndex;
|
||||
[NonSerialized] private float lastWidth;
|
||||
[NonSerialized] private int listID;
|
||||
[NonSerialized] private int newSelectionIndex;
|
||||
[NonSerialized] private float scrollOffset;
|
||||
[NonSerialized] private DateTimeOffset scrollTime = DateTimeOffset.Now;
|
||||
[NonSerialized] private int selectionIndex;
|
||||
[NonSerialized] private int statusAhead;
|
||||
[NonSerialized] private int statusBehind;
|
||||
[NonSerialized] private bool updated = true;
|
||||
[NonSerialized] private bool useScrollTime;
|
||||
|
||||
const string
|
||||
HistoryFocusAll = "(All)",
|
||||
HistoryFocusSingle = "Focus: <b>{0}</b>",
|
||||
PullButton = "Pull",
|
||||
PullButtonCount = "Pull ({<b>0</b>})",
|
||||
PushButton = "Push",
|
||||
PushButtonCount = "Push (<b>{0}</b>)",
|
||||
PullConfirmTitle = "Pull Changes?",
|
||||
PullConfirmDescription = "Would you like to pull changes from remote '{0}'?",
|
||||
PullConfirmYes = "Pull",
|
||||
PullConfirmCancel = "Cancel",
|
||||
PushConfirmTitle = "Push Changes?",
|
||||
PushConfirmDescription = "Would you like to push changes to remote '{0}'?",
|
||||
PushConfirmYes = "Push",
|
||||
PushConfirmCancel = "Cancel",
|
||||
ClearSelectionButton = "x";
|
||||
const int
|
||||
HistoryExtraItemCount = 10;
|
||||
const float
|
||||
MaxChangelistHeightRatio = .2f;
|
||||
|
||||
|
||||
[SerializeField] List<GitLogEntry> history = new List<GitLogEntry>();
|
||||
[SerializeField] bool historyLocked = true;
|
||||
[SerializeField] Object historyTarget = null;
|
||||
[SerializeField] Vector2 scroll;
|
||||
[SerializeField] string selectionID;
|
||||
[SerializeField] ChangesetTreeView changesetTree = new ChangesetTreeView();
|
||||
[SerializeField] Vector2 detailsScroll;
|
||||
[SerializeField] bool broadMode = false;
|
||||
|
||||
|
||||
string currentRemote = "placeholder";
|
||||
int
|
||||
historyStartIndex,
|
||||
historyStopIndex,
|
||||
statusAhead,
|
||||
statusBehind;
|
||||
bool
|
||||
updated = true,
|
||||
useScrollTime = false;
|
||||
DateTimeOffset scrollTime = DateTimeOffset.Now;
|
||||
float
|
||||
lastWidth,
|
||||
scrollOffset;
|
||||
int
|
||||
listID,
|
||||
selectionIndex,
|
||||
newSelectionIndex;
|
||||
|
||||
|
||||
public bool BroadMode { get { return broadMode; } }
|
||||
|
||||
|
||||
float EntryHeight
|
||||
{
|
||||
get
|
||||
{
|
||||
return Styles.HistoryEntryHeight + Styles.HistoryEntryPadding;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void OnShow()
|
||||
{
|
||||
lastWidth = position.width;
|
||||
selectionIndex = newSelectionIndex = -1;
|
||||
|
||||
GitLogTask.RegisterCallback(OnLogUpdate);
|
||||
GitStatusTask.RegisterCallback(OnStatusUpdate);
|
||||
|
||||
changesetTree.Show(this);
|
||||
changesetTree.Readonly = true;
|
||||
}
|
||||
|
||||
|
||||
protected override void OnHide()
|
||||
{
|
||||
GitStatusTask.UnregisterCallback(OnStatusUpdate);
|
||||
GitLogTask.UnregisterCallback(OnLogUpdate);
|
||||
}
|
||||
|
||||
[SerializeField] private bool broadMode;
|
||||
[SerializeField] private ChangesetTreeView changesetTree = new ChangesetTreeView();
|
||||
[SerializeField] private Vector2 detailsScroll;
|
||||
[SerializeField] private List<GitLogEntry> history = new List<GitLogEntry>();
|
||||
[SerializeField] private bool historyLocked = true;
|
||||
[SerializeField] private Object historyTarget;
|
||||
[SerializeField] private Vector2 scroll;
|
||||
[SerializeField] private string selectionID;
|
||||
|
||||
public override void Refresh()
|
||||
{
|
||||
|
@ -122,68 +70,6 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void OnStatusUpdate(GitStatus update)
|
||||
{
|
||||
// Set branch state
|
||||
// TODO: Update currentRemote
|
||||
statusAhead = update.Ahead;
|
||||
statusBehind = update.Behind;
|
||||
}
|
||||
|
||||
|
||||
void OnLogUpdate(IList<GitLogEntry> entries)
|
||||
{
|
||||
updated = true;
|
||||
|
||||
history.Clear();
|
||||
history.AddRange(entries);
|
||||
|
||||
if (history.Any())
|
||||
{
|
||||
// Make sure that scroll as much as possible focuses the same time period in the new entry list
|
||||
if (useScrollTime)
|
||||
{
|
||||
int closestIndex = -1;
|
||||
double closestDifference = Mathf.Infinity;
|
||||
for (int index = 0; index < history.Count; ++index)
|
||||
{
|
||||
double diff = Math.Abs((history[index].Time - scrollTime).TotalSeconds);
|
||||
if (diff < closestDifference)
|
||||
{
|
||||
closestDifference = diff;
|
||||
closestIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
ScrollTo(closestIndex, scrollOffset);
|
||||
}
|
||||
|
||||
CullHistory();
|
||||
}
|
||||
|
||||
// Restore selection index or clear it
|
||||
newSelectionIndex = -1;
|
||||
if (!string.IsNullOrEmpty(selectionID))
|
||||
{
|
||||
selectionIndex = Enumerable.Range(1, history.Count + 1).FirstOrDefault(index => history[index - 1].CommitID.Equals(selectionID)) - 1;
|
||||
|
||||
if (selectionIndex < 0)
|
||||
{
|
||||
selectionID = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
Repaint();
|
||||
}
|
||||
|
||||
|
||||
void ScrollTo(int index, float offset = 0f)
|
||||
{
|
||||
scroll.Set(scroll.x, EntryHeight * index + offset);
|
||||
}
|
||||
|
||||
|
||||
public override void OnSelectionChange()
|
||||
{
|
||||
if (!historyLocked && !string.IsNullOrEmpty(AssetDatabase.GetAssetPath(Selection.activeObject)))
|
||||
|
@ -193,10 +79,9 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public bool EvaluateBroadMode()
|
||||
{
|
||||
bool past = broadMode;
|
||||
var past = broadMode;
|
||||
|
||||
// Flip when the limits are breached
|
||||
if (position.width > Styles.BroadModeLimit)
|
||||
|
@ -209,9 +94,8 @@ namespace GitHub.Unity
|
|||
}
|
||||
|
||||
// Show the layout notification while scaling
|
||||
|
||||
Window window = (Window)parent;
|
||||
bool scaled = position.width != lastWidth;
|
||||
var window = (Window)parent;
|
||||
var scaled = position.width != lastWidth;
|
||||
lastWidth = position.width;
|
||||
|
||||
if (scaled)
|
||||
|
@ -223,7 +107,6 @@ namespace GitHub.Unity
|
|||
return broadMode != past;
|
||||
}
|
||||
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (broadMode)
|
||||
|
@ -241,62 +124,68 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public void OnBroadGUI()
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.BeginVertical(
|
||||
GUILayout.MinWidth(Styles.BroadModeBranchesMinWidth),
|
||||
GUILayout.MaxWidth(Mathf.Max(Styles.BroadModeBranchesMinWidth, position.width * Styles.BroadModeBranchesRatio))
|
||||
);
|
||||
{
|
||||
((Window)parent).BranchesTab.OnEmbeddedGUI();
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.BeginVertical();
|
||||
{
|
||||
OnEmbeddedGUI();
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
|
||||
public void OnEmbeddedGUI()
|
||||
{
|
||||
// History toolbar
|
||||
GUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
{
|
||||
// Target indicator / clear button
|
||||
EditorGUI.BeginDisabledGroup(historyTarget == null);
|
||||
{
|
||||
if (GUILayout.Button(
|
||||
historyTarget == null ? HistoryFocusAll : string.Format(HistoryFocusSingle, historyTarget.name),
|
||||
Styles.HistoryToolbarButtonStyle
|
||||
))
|
||||
historyTarget == null ? HistoryFocusAll : String.Format(HistoryFocusSingle, historyTarget.name),
|
||||
Styles.HistoryToolbarButtonStyle)
|
||||
)
|
||||
{
|
||||
historyTarget = null;
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
// Pull / Push buttons
|
||||
if (
|
||||
GUILayout.Button(statusBehind > 0 ? string.Format(PullButtonCount, statusBehind) : PullButton, Styles.HistoryToolbarButtonStyle) &&
|
||||
EditorUtility.DisplayDialog(
|
||||
PullConfirmTitle,
|
||||
string.Format(PullConfirmDescription, currentRemote),
|
||||
var pullButtonText = statusBehind > 0 ? String.Format(PullButtonCount, statusBehind) : PullButton;
|
||||
var pullClicked = GUILayout.Button(pullButtonText, Styles.HistoryToolbarButtonStyle);
|
||||
if (pullClicked &&
|
||||
EditorUtility.DisplayDialog(PullConfirmTitle,
|
||||
String.Format(PullConfirmDescription, currentRemote),
|
||||
PullConfirmYes,
|
||||
PullConfirmCancel
|
||||
)
|
||||
PullConfirmCancel)
|
||||
)
|
||||
{
|
||||
Pull();
|
||||
}
|
||||
if (
|
||||
GUILayout.Button(statusAhead > 0 ? string.Format(PushButtonCount, statusAhead) : PushButton, Styles.HistoryToolbarButtonStyle) &&
|
||||
EditorUtility.DisplayDialog(
|
||||
PushConfirmTitle,
|
||||
string.Format(PushConfirmDescription, currentRemote),
|
||||
|
||||
var pushButtonText = statusAhead > 0 ? String.Format(PushButtonCount, statusAhead) : PushButton;
|
||||
var pushClicked = GUILayout.Button(pushButtonText, Styles.HistoryToolbarButtonStyle);
|
||||
if (pushClicked &&
|
||||
EditorUtility.DisplayDialog(PushConfirmTitle,
|
||||
String.Format(PushConfirmDescription, currentRemote),
|
||||
PushConfirmYes,
|
||||
PushConfirmCancel
|
||||
)
|
||||
PushConfirmCancel)
|
||||
)
|
||||
{
|
||||
Push();
|
||||
|
@ -304,11 +193,14 @@ namespace GitHub.Unity
|
|||
|
||||
// Target lock button
|
||||
EditorGUI.BeginChangeCheck();
|
||||
{
|
||||
historyLocked = GUILayout.Toggle(historyLocked, GUIContent.none, Styles.HistoryLockStyle);
|
||||
}
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
OnSelectionChange();
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// When history scroll actually changes, store time value of topmost visible entry. This is the value we use to reposition scroll on log update - not the pixel value.
|
||||
|
@ -317,55 +209,56 @@ namespace GitHub.Unity
|
|||
listID = GUIUtility.GetControlID(FocusType.Keyboard);
|
||||
|
||||
// Only update time scroll
|
||||
Vector2 lastScroll = scroll;
|
||||
var lastScroll = scroll;
|
||||
scroll = GUILayout.BeginScrollView(scroll);
|
||||
if (lastScroll != scroll && !updated)
|
||||
if (lastScroll != scroll && !updated)
|
||||
{
|
||||
scrollTime = history[historyStartIndex].Time;
|
||||
scrollOffset = scroll.y - historyStartIndex * EntryHeight;
|
||||
useScrollTime = true;
|
||||
}
|
||||
// Handle only the selected range of history items - adding spacing for the rest
|
||||
var start = Mathf.Max(0, historyStartIndex - HistoryExtraItemCount);
|
||||
var stop = Mathf.Min(historyStopIndex + HistoryExtraItemCount, history.Count);
|
||||
GUILayout.Space(start * EntryHeight);
|
||||
for (var index = start; index < stop; ++index)
|
||||
{
|
||||
if (HistoryEntry(history[index], GetEntryState(index), selectionIndex == index))
|
||||
{
|
||||
scrollTime = history[historyStartIndex].Time;
|
||||
scrollOffset = scroll.y - historyStartIndex * EntryHeight;
|
||||
useScrollTime = true;
|
||||
newSelectionIndex = index;
|
||||
GUIUtility.keyboardControl = listID;
|
||||
}
|
||||
// Handle only the selected range of history items - adding spacing for the rest
|
||||
int
|
||||
start = Mathf.Max(0, historyStartIndex - HistoryExtraItemCount),
|
||||
stop = Mathf.Min(historyStopIndex + HistoryExtraItemCount, history.Count);
|
||||
GUILayout.Space(start * EntryHeight);
|
||||
for (int index = start; index < stop; ++index)
|
||||
|
||||
GUILayout.Space(Styles.HistoryEntryPadding);
|
||||
}
|
||||
|
||||
GUILayout.Space((history.Count - stop) * EntryHeight);
|
||||
|
||||
// Keyboard control
|
||||
if (GUIUtility.keyboardControl == listID && Event.current.type == EventType.KeyDown)
|
||||
{
|
||||
var change = 0;
|
||||
|
||||
if (Event.current.keyCode == KeyCode.DownArrow)
|
||||
{
|
||||
if (HistoryEntry(history[index], GetEntryState(index), selectionIndex == index))
|
||||
{
|
||||
newSelectionIndex = index;
|
||||
GUIUtility.keyboardControl = listID;
|
||||
}
|
||||
|
||||
GUILayout.Space(Styles.HistoryEntryPadding);
|
||||
change = 1;
|
||||
}
|
||||
GUILayout.Space((history.Count - stop) * EntryHeight);
|
||||
|
||||
// Keyboard control
|
||||
if (GUIUtility.keyboardControl == listID && Event.current.type == EventType.KeyDown)
|
||||
else if (Event.current.keyCode == KeyCode.UpArrow)
|
||||
{
|
||||
int change = 0;
|
||||
|
||||
if (Event.current.keyCode == KeyCode.DownArrow)
|
||||
{
|
||||
change = 1;
|
||||
}
|
||||
else if (Event.current.keyCode == KeyCode.UpArrow)
|
||||
{
|
||||
change = -1;
|
||||
}
|
||||
|
||||
if (change != 0)
|
||||
{
|
||||
newSelectionIndex = (selectionIndex + change) % history.Count;
|
||||
if (newSelectionIndex < historyStartIndex || newSelectionIndex > historyStopIndex)
|
||||
{
|
||||
ScrollTo(newSelectionIndex, (position.height - position.height * MaxChangelistHeightRatio - 30f - EntryHeight) * -.5f);
|
||||
}
|
||||
Event.current.Use();
|
||||
}
|
||||
change = -1;
|
||||
}
|
||||
|
||||
if (change != 0)
|
||||
{
|
||||
newSelectionIndex = (selectionIndex + change) % history.Count;
|
||||
if (newSelectionIndex < historyStartIndex || newSelectionIndex > historyStopIndex)
|
||||
{
|
||||
ScrollTo(newSelectionIndex,
|
||||
(position.height - position.height * MaxChangelistHeightRatio - 30f - EntryHeight) * -.5f);
|
||||
}
|
||||
Event.current.Use();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -377,10 +270,11 @@ namespace GitHub.Unity
|
|||
// Selection info
|
||||
if (selectionIndex >= 0)
|
||||
{
|
||||
GitLogEntry selection = history[selectionIndex];
|
||||
var selection = history[selectionIndex];
|
||||
|
||||
// Top bar for scrolling to selection or clearing it
|
||||
GUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
{
|
||||
if (GUILayout.Button(selection.ShortID, Styles.HistoryToolbarButtonStyle))
|
||||
{
|
||||
ScrollTo(selectionIndex);
|
||||
|
@ -389,25 +283,28 @@ namespace GitHub.Unity
|
|||
{
|
||||
newSelectionIndex = -2;
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// Log entry details - including changeset tree (if any changes are found)
|
||||
if (changesetTree.Entries.Any())
|
||||
{
|
||||
detailsScroll = GUILayout.BeginScrollView(
|
||||
detailsScroll,
|
||||
GUILayout.MinHeight(Mathf.Min(changesetTree.Height, position.height * MaxChangelistHeightRatio))
|
||||
);
|
||||
detailsScroll = GUILayout.BeginScrollView(detailsScroll,
|
||||
GUILayout.MinHeight(Mathf.Min(changesetTree.Height, position.height * MaxChangelistHeightRatio)));
|
||||
{
|
||||
HistoryEntry(selection, GetEntryState(selectionIndex), false);
|
||||
|
||||
GUILayout.Space(EditorGUIUtility.standardVerticalSpacing);
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.Space(Styles.HistoryChangesIndentation);
|
||||
changesetTree.OnGUI();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(EditorGUIUtility.standardVerticalSpacing);
|
||||
}
|
||||
GUILayout.EndScrollView();
|
||||
}
|
||||
else
|
||||
|
@ -438,43 +335,119 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
protected override void OnShow()
|
||||
{
|
||||
lastWidth = position.width;
|
||||
selectionIndex = newSelectionIndex = -1;
|
||||
|
||||
LogEntryState GetEntryState(int index)
|
||||
GitLogTask.RegisterCallback(OnLogUpdate);
|
||||
GitStatusTask.RegisterCallback(OnStatusUpdate);
|
||||
|
||||
changesetTree.Show(this);
|
||||
changesetTree.Readonly = true;
|
||||
}
|
||||
|
||||
protected override void OnHide()
|
||||
{
|
||||
GitStatusTask.UnregisterCallback(OnStatusUpdate);
|
||||
GitLogTask.UnregisterCallback(OnLogUpdate);
|
||||
}
|
||||
|
||||
private void OnStatusUpdate(GitStatus update)
|
||||
{
|
||||
// Set branch state
|
||||
// TODO: Update currentRemote
|
||||
statusAhead = update.Ahead;
|
||||
statusBehind = update.Behind;
|
||||
}
|
||||
|
||||
private void OnLogUpdate(IList<GitLogEntry> entries)
|
||||
{
|
||||
updated = true;
|
||||
|
||||
history.Clear();
|
||||
history.AddRange(entries);
|
||||
|
||||
if (history.Any())
|
||||
{
|
||||
// Make sure that scroll as much as possible focuses the same time period in the new entry list
|
||||
if (useScrollTime)
|
||||
{
|
||||
var closestIndex = -1;
|
||||
double closestDifference = Mathf.Infinity;
|
||||
for (var index = 0; index < history.Count; ++index)
|
||||
{
|
||||
var diff = Math.Abs((history[index].Time - scrollTime).TotalSeconds);
|
||||
if (diff < closestDifference)
|
||||
{
|
||||
closestDifference = diff;
|
||||
closestIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
ScrollTo(closestIndex, scrollOffset);
|
||||
}
|
||||
|
||||
CullHistory();
|
||||
}
|
||||
|
||||
// Restore selection index or clear it
|
||||
newSelectionIndex = -1;
|
||||
if (!string.IsNullOrEmpty(selectionID))
|
||||
{
|
||||
selectionIndex =
|
||||
Enumerable.Range(1, history.Count + 1).FirstOrDefault(index => history[index - 1].CommitID.Equals(selectionID)) - 1;
|
||||
|
||||
if (selectionIndex < 0)
|
||||
{
|
||||
selectionID = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private void ScrollTo(int index, float offset = 0f)
|
||||
{
|
||||
scroll.Set(scroll.x, EntryHeight * index + offset);
|
||||
}
|
||||
|
||||
private LogEntryState GetEntryState(int index)
|
||||
{
|
||||
return historyTarget == null ? (index < statusAhead ? LogEntryState.Local : LogEntryState.Normal) : LogEntryState.Normal;
|
||||
}
|
||||
|
||||
|
||||
void CullHistory()
|
||||
// Recalculate the range of history items to handle - based on what is visible, plus a bit of padding for fast scrolling
|
||||
/// <summary>
|
||||
/// Recalculate the range of history items to handle - based on what is visible, plus a bit of padding for fast scrolling
|
||||
/// </summary>
|
||||
private void CullHistory()
|
||||
{
|
||||
historyStartIndex = (int)Mathf.Clamp(scroll.y / EntryHeight, 0, history.Count);
|
||||
historyStopIndex = (int)Mathf.Clamp(
|
||||
historyStartIndex + (position.height - 2f * Mathf.Min(changesetTree.Height, position.height * MaxChangelistHeightRatio)) / EntryHeight +
|
||||
1,
|
||||
0,
|
||||
history.Count
|
||||
);
|
||||
historyStopIndex =
|
||||
(int)
|
||||
Mathf.Clamp(
|
||||
historyStartIndex +
|
||||
(position.height - 2f * Mathf.Min(changesetTree.Height, position.height * MaxChangelistHeightRatio)) /
|
||||
EntryHeight + 1, 0, history.Count);
|
||||
}
|
||||
|
||||
|
||||
bool HistoryEntry(GitLogEntry entry, LogEntryState state, bool selected)
|
||||
private bool HistoryEntry(GitLogEntry entry, LogEntryState state, bool selected)
|
||||
{
|
||||
Rect entryRect = GUILayoutUtility.GetRect(Styles.HistoryEntryHeight, Styles.HistoryEntryHeight);
|
||||
var entryRect = GUILayoutUtility.GetRect(Styles.HistoryEntryHeight, Styles.HistoryEntryHeight);
|
||||
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
bool keyboardFocus = GUIUtility.keyboardControl == listID;
|
||||
var keyboardFocus = GUIUtility.keyboardControl == listID;
|
||||
|
||||
Rect
|
||||
summaryRect = new Rect(entryRect.x, entryRect.y, entryRect.width, Styles.HistorySummaryHeight),
|
||||
timestampRect = new Rect(entryRect.x, entryRect.yMax - Styles.HistoryDetailsHeight, entryRect.width * .5f, Styles.HistoryDetailsHeight);
|
||||
Rect authorRect = new Rect(timestampRect.xMax, timestampRect.y, timestampRect.width, timestampRect.height);
|
||||
var summaryRect = new Rect(entryRect.x, entryRect.y, entryRect.width, Styles.HistorySummaryHeight);
|
||||
var timestampRect = new Rect(entryRect.x, entryRect.yMax - Styles.HistoryDetailsHeight, entryRect.width * .5f,
|
||||
Styles.HistoryDetailsHeight);
|
||||
var authorRect = new Rect(timestampRect.xMax, timestampRect.y, timestampRect.width, timestampRect.height);
|
||||
|
||||
if (!string.IsNullOrEmpty(entry.MergeA))
|
||||
{
|
||||
const float MergeIndicatorSize = 40f;
|
||||
Rect mergeIndicatorRect = new Rect(summaryRect.x, summaryRect.y, MergeIndicatorSize, summaryRect.height);
|
||||
var mergeIndicatorRect = new Rect(summaryRect.x, summaryRect.y, MergeIndicatorSize, summaryRect.height);
|
||||
|
||||
// TODO: Get an icon or something here
|
||||
Styles.HistoryEntryDetailsStyle.Draw(mergeIndicatorRect, "Merge:", false, false, selected, keyboardFocus);
|
||||
|
@ -485,7 +458,7 @@ namespace GitHub.Unity
|
|||
if (state == LogEntryState.Local)
|
||||
{
|
||||
const float LocalIndicatorSize = 40f;
|
||||
Rect localIndicatorRect = new Rect(summaryRect.x, summaryRect.y, LocalIndicatorSize, summaryRect.height);
|
||||
var localIndicatorRect = new Rect(summaryRect.x, summaryRect.y, LocalIndicatorSize, summaryRect.height);
|
||||
|
||||
// TODO: Get an icon or something here
|
||||
Styles.HistoryEntryDetailsStyle.Draw(localIndicatorRect, "Local:", false, false, selected, keyboardFocus);
|
||||
|
@ -506,16 +479,31 @@ namespace GitHub.Unity
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
void Pull()
|
||||
private void Pull()
|
||||
{
|
||||
Debug.Log("TODO: Pull");
|
||||
}
|
||||
|
||||
|
||||
void Push()
|
||||
private void Push()
|
||||
{
|
||||
Debug.Log("TODO: Push");
|
||||
}
|
||||
|
||||
public bool BroadMode
|
||||
{
|
||||
get { return broadMode; }
|
||||
}
|
||||
|
||||
private float EntryHeight
|
||||
{
|
||||
get { return Styles.HistoryEntryHeight + Styles.HistoryEntryPadding; }
|
||||
}
|
||||
|
||||
private enum LogEntryState
|
||||
{
|
||||
Normal,
|
||||
Local,
|
||||
Remote
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,80 +1,69 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
[System.Serializable]
|
||||
[Serializable]
|
||||
class SettingsView : Subview
|
||||
{
|
||||
const string
|
||||
EditorSettingsMissingTitle = "Missing editor settings",
|
||||
EditorSettingsMissingMessage = "No valid editor settings found when looking in expected path '{0}'. Please save the project.",
|
||||
BadVCSSettingsTitle = "Update settings",
|
||||
BadVCSSettingsMessage = "To use Git, you will need to set project Version Control Mode to either 'Visible Meta Files' or 'Hidden Meta Files'.",
|
||||
SelectEditorSettingsButton = "View settings",
|
||||
NoActiveRepositoryTitle = "No repository found",
|
||||
NoActiveRepositoryMessage = "Your current project is not currently in an active Git repository:",
|
||||
TextSerialisationMessage = "For optimal Git use, it is recommended that you configure Unity to serialize assets using text serialization. Note that this may cause editor slowdowns for projects with very large datasets.",
|
||||
BinarySerialisationMessage = "This project is currently configured for binary serialization.",
|
||||
MixedSerialisationMessage = "This project is currently configured for mixed serialization.",
|
||||
IgnoreSerialisationIssuesSetting = "IgnoreSerializationIssues",
|
||||
IgnoreSerialisationSettingsButton = "Ignore forever",
|
||||
RefreshIssuesButton = "Refresh",
|
||||
GitIgnoreExceptionWarning = "Exception when searching .gitignore files: {0}",
|
||||
GitIgnoreIssueWarning = "{0}: {2}\n\nIn line \"{1}\"",
|
||||
GitIgnoreIssueNoLineWarning = "{0}: {1}",
|
||||
GitInitBrowseTitle = "Pick desired repository root",
|
||||
GitInitButton = "Set up Git",
|
||||
InvalidInitDirectoryTitle = "Invalid repository root",
|
||||
InvalidInitDirectoryMessage = "Your selected folder '{0}' is not a valid repository root for your current project.",
|
||||
InvalidInitDirectoryOK = "OK",
|
||||
GitInstallTitle = "Git install",
|
||||
GitInstallMissingMessage = "GitHub was unable to locate a valid Git install. Please specify install location or install git.",
|
||||
GitInstallBrowseTitle = "Select git binary",
|
||||
GitInstallPickInvalidTitle = "Invalid Git install",
|
||||
GitInstallPickInvalidMessage = "The selected file is not a valid Git install.",
|
||||
GitInstallPickInvalidOK = "OK",
|
||||
GitInstallFindButton = "Find install",
|
||||
GitInstallButton = "New Git install",
|
||||
GitInstallURL = "http://desktop.github.com",
|
||||
GitIgnoreRulesTitle = "gitignore rules",
|
||||
GitIgnoreRulesEffect = "Effect",
|
||||
GitIgnoreRulesFile = "File",
|
||||
GitIgnoreRulesLine = "Line",
|
||||
GitIgnoreRulesDescription = "Description",
|
||||
NewGitIgnoreRuleButton = "New",
|
||||
DeleteGitIgnoreRuleButton = "Delete",
|
||||
RemotesTitle = "Remotes",
|
||||
RemoteNameTitle = "Name",
|
||||
RemoteUserTitle = "User",
|
||||
RemoteHostTitle = "Host",
|
||||
RemoteAccessTitle = "Access";
|
||||
private const string EditorSettingsMissingTitle = "Missing editor settings";
|
||||
private const string EditorSettingsMissingMessage =
|
||||
"No valid editor settings found when looking in expected path '{0}'. Please save the project.";
|
||||
private const string BadVCSSettingsTitle = "Update settings";
|
||||
private const string BadVCSSettingsMessage =
|
||||
"To use Git, you will need to set project Version Control Mode to either 'Visible Meta Files' or 'Hidden Meta Files'.";
|
||||
private const string SelectEditorSettingsButton = "View settings";
|
||||
private const string NoActiveRepositoryTitle = "No repository found";
|
||||
private const string NoActiveRepositoryMessage = "Your current project is not currently in an active Git repository:";
|
||||
private const string TextSerialisationMessage =
|
||||
"For optimal Git use, it is recommended that you configure Unity to serialize assets using text serialization. Note that this may cause editor slowdowns for projects with very large datasets.";
|
||||
private const string BinarySerialisationMessage = "This project is currently configured for binary serialization.";
|
||||
private const string MixedSerialisationMessage = "This project is currently configured for mixed serialization.";
|
||||
private const string IgnoreSerialisationIssuesSetting = "IgnoreSerializationIssues";
|
||||
private const string IgnoreSerialisationSettingsButton = "Ignore forever";
|
||||
private const string RefreshIssuesButton = "Refresh";
|
||||
private const string GitIgnoreExceptionWarning = "Exception when searching .gitignore files: {0}";
|
||||
private const string GitIgnoreIssueWarning = "{0}: {2}\n\nIn line \"{1}\"";
|
||||
private const string GitIgnoreIssueNoLineWarning = "{0}: {1}";
|
||||
private const string GitInitBrowseTitle = "Pick desired repository root";
|
||||
private const string GitInitButton = "Set up Git";
|
||||
private const string InvalidInitDirectoryTitle = "Invalid repository root";
|
||||
private const string InvalidInitDirectoryMessage =
|
||||
"Your selected folder '{0}' is not a valid repository root for your current project.";
|
||||
private const string InvalidInitDirectoryOK = "OK";
|
||||
private const string GitInstallTitle = "Git install";
|
||||
private const string GitInstallMissingMessage =
|
||||
"GitHub was unable to locate a valid Git install. Please specify install location or install git.";
|
||||
private const string GitInstallBrowseTitle = "Select git binary";
|
||||
private const string GitInstallPickInvalidTitle = "Invalid Git install";
|
||||
private const string GitInstallPickInvalidMessage = "The selected file is not a valid Git install. {0}";
|
||||
private const string GitInstallPickInvalidOK = "OK";
|
||||
private const string GitInstallFindButton = "Find install";
|
||||
private const string GitInstallButton = "New Git install";
|
||||
private const string GitInstallURL = "http://desktop.github.com";
|
||||
private const string GitIgnoreRulesTitle = "gitignore rules";
|
||||
private const string GitIgnoreRulesEffect = "Effect";
|
||||
private const string GitIgnoreRulesFile = "File";
|
||||
private const string GitIgnoreRulesLine = "Line";
|
||||
private const string GitIgnoreRulesDescription = "Description";
|
||||
private const string NewGitIgnoreRuleButton = "New";
|
||||
private const string DeleteGitIgnoreRuleButton = "Delete";
|
||||
private const string RemotesTitle = "Remotes";
|
||||
private const string RemoteNameTitle = "Name";
|
||||
private const string RemoteUserTitle = "User";
|
||||
private const string RemoteHostTitle = "Host";
|
||||
private const string RemoteAccessTitle = "Access";
|
||||
|
||||
[NonSerialized] private int newGitIgnoreRulesSelection = -1;
|
||||
|
||||
[SerializeField] List<GitRemote> remotes = new List<GitRemote>();
|
||||
[SerializeField] string initDirectory;
|
||||
[SerializeField] int gitIgnoreRulesSelection = 0;
|
||||
[SerializeField] Vector2 scroll;
|
||||
|
||||
|
||||
int newGitIgnoreRulesSelection = -1;
|
||||
|
||||
|
||||
protected override void OnShow()
|
||||
{
|
||||
GitListRemotesTask.RegisterCallback(OnRemotesUpdate);
|
||||
}
|
||||
|
||||
|
||||
protected override void OnHide()
|
||||
{
|
||||
GitListRemotesTask.UnregisterCallback(OnRemotesUpdate);
|
||||
}
|
||||
|
||||
[SerializeField] private int gitIgnoreRulesSelection = 0;
|
||||
[SerializeField] private string initDirectory;
|
||||
[SerializeField] private List<GitRemote> remotes = new List<GitRemote>();
|
||||
[SerializeField] private Vector2 scroll;
|
||||
|
||||
public override void Refresh()
|
||||
{
|
||||
|
@ -82,20 +71,11 @@ namespace GitHub.Unity
|
|||
GitStatusTask.Schedule();
|
||||
}
|
||||
|
||||
|
||||
void OnRemotesUpdate(IList<GitRemote> entries)
|
||||
{
|
||||
remotes.Clear();
|
||||
remotes.AddRange(entries);
|
||||
Repaint();
|
||||
}
|
||||
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
scroll = GUILayout.BeginScrollView(scroll);
|
||||
{
|
||||
// Issues
|
||||
|
||||
if (!OnIssuesGUI())
|
||||
{
|
||||
return;
|
||||
|
@ -127,6 +107,8 @@ namespace GitHub.Unity
|
|||
|
||||
GUILayout.Label(GitInstallTitle, EditorStyles.boldLabel);
|
||||
OnInstallPathGUI();
|
||||
}
|
||||
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
// Effectuate new selection at end of frame
|
||||
|
@ -138,19 +120,62 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
bool OnIssuesGUI()
|
||||
private static void TableCell(string label, float width)
|
||||
{
|
||||
ProjectSettingsIssue settingsIssues = Utility.Issues.Select(i => i as ProjectSettingsIssue).FirstOrDefault(i => i != null);
|
||||
GUILayout.Label(label, EditorStyles.miniLabel, GUILayout.Width(width), GUILayout.MaxWidth(width));
|
||||
}
|
||||
|
||||
private static bool ValidateInitDirectory(string path)
|
||||
{
|
||||
if (Utility.UnityProjectPath.IndexOf(path) != 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog(InvalidInitDirectoryTitle, String.Format(InvalidInitDirectoryMessage, path),
|
||||
InvalidInitDirectoryOK);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool ValidateGitInstall(string path)
|
||||
{
|
||||
if (!FindGitTask.ValidateGitInstall(path))
|
||||
{
|
||||
EditorUtility.DisplayDialog(GitInstallPickInvalidTitle, String.Format(GitInstallPickInvalidMessage, path),
|
||||
GitInstallPickInvalidOK);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnShow()
|
||||
{
|
||||
GitListRemotesTask.RegisterCallback(OnRemotesUpdate);
|
||||
}
|
||||
|
||||
protected override void OnHide()
|
||||
{
|
||||
GitListRemotesTask.UnregisterCallback(OnRemotesUpdate);
|
||||
}
|
||||
|
||||
private void OnRemotesUpdate(IList<GitRemote> entries)
|
||||
{
|
||||
remotes.Clear();
|
||||
remotes.AddRange(entries);
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private bool OnIssuesGUI()
|
||||
{
|
||||
var settingsIssues = Utility.Issues.Select(i => i as ProjectSettingsIssue).FirstOrDefault(i => i != null);
|
||||
|
||||
if (settingsIssues != null)
|
||||
{
|
||||
if (settingsIssues.WasCaught(ProjectSettingsEvaluation.EditorSettingsMissing))
|
||||
{
|
||||
Styles.BeginInitialStateArea(
|
||||
EditorSettingsMissingTitle,
|
||||
string.Format(EditorSettingsMissingMessage, EvaluateProjectConfigurationTask.EditorSettingsPath)
|
||||
);
|
||||
Styles.BeginInitialStateArea(EditorSettingsMissingTitle,
|
||||
String.Format(EditorSettingsMissingMessage, EvaluateProjectConfigurationTask.EditorSettingsPath));
|
||||
Styles.EndInitialStateArea();
|
||||
|
||||
return false;
|
||||
|
@ -158,6 +183,7 @@ namespace GitHub.Unity
|
|||
else if (settingsIssues.WasCaught(ProjectSettingsEvaluation.BadVCSSettings))
|
||||
{
|
||||
Styles.BeginInitialStateArea(BadVCSSettingsTitle, BadVCSSettingsMessage);
|
||||
{
|
||||
GUILayout.Space(EditorGUIUtility.standardVerticalSpacing);
|
||||
|
||||
// Button to select editor settings - for remedying the bad setting
|
||||
|
@ -165,6 +191,7 @@ namespace GitHub.Unity
|
|||
{
|
||||
Selection.activeObject = EvaluateProjectConfigurationTask.LoadEditorSettings();
|
||||
}
|
||||
}
|
||||
Styles.EndInitialStateArea();
|
||||
|
||||
return false;
|
||||
|
@ -174,7 +201,9 @@ namespace GitHub.Unity
|
|||
if (!Utility.GitFound)
|
||||
{
|
||||
Styles.BeginInitialStateArea(GitInstallTitle, GitInstallMissingMessage);
|
||||
{
|
||||
OnInstallPathGUI();
|
||||
}
|
||||
Styles.EndInitialStateArea();
|
||||
|
||||
return false;
|
||||
|
@ -182,8 +211,10 @@ namespace GitHub.Unity
|
|||
else if (!Utility.ActiveRepository)
|
||||
{
|
||||
Styles.BeginInitialStateArea(NoActiveRepositoryTitle, NoActiveRepositoryMessage);
|
||||
{
|
||||
// Init directory path field
|
||||
Styles.PathField(ref initDirectory, () => EditorUtility.OpenFolderPanel(GitInitBrowseTitle, initDirectory, ""), ValidateInitDirectory);
|
||||
Styles.PathField(ref initDirectory, () => EditorUtility.OpenFolderPanel(GitInitBrowseTitle, initDirectory, ""),
|
||||
ValidateInitDirectory);
|
||||
|
||||
GUILayout.Space(EditorGUIUtility.standardVerticalSpacing);
|
||||
|
||||
|
@ -199,6 +230,7 @@ namespace GitHub.Unity
|
|||
ResetInitDirectory();
|
||||
}
|
||||
}
|
||||
}
|
||||
Styles.EndInitialStateArea();
|
||||
|
||||
return false;
|
||||
|
@ -206,9 +238,8 @@ namespace GitHub.Unity
|
|||
|
||||
if (settingsIssues != null && !Settings.Get(IgnoreSerialisationIssuesSetting, "0").Equals("1"))
|
||||
{
|
||||
bool
|
||||
binary = settingsIssues.WasCaught(ProjectSettingsEvaluation.BinarySerialization),
|
||||
mixed = settingsIssues.WasCaught(ProjectSettingsEvaluation.MixedSerialization);
|
||||
var binary = settingsIssues.WasCaught(ProjectSettingsEvaluation.BinarySerialization);
|
||||
var mixed = settingsIssues.WasCaught(ProjectSettingsEvaluation.MixedSerialization);
|
||||
|
||||
if (binary || mixed)
|
||||
{
|
||||
|
@ -216,6 +247,7 @@ namespace GitHub.Unity
|
|||
Styles.Warning(binary ? BinarySerialisationMessage : MixedSerialisationMessage);
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
if (GUILayout.Button(IgnoreSerialisationSettingsButton))
|
||||
{
|
||||
Settings.Set(IgnoreSerialisationIssuesSetting, "1");
|
||||
|
@ -232,173 +264,177 @@ namespace GitHub.Unity
|
|||
{
|
||||
Selection.activeObject = EvaluateProjectConfigurationTask.LoadEditorSettings();
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
GitIgnoreException gitIgnoreException = Utility.Issues.Select(i => i as GitIgnoreException).FirstOrDefault(i => i != null);
|
||||
var gitIgnoreException = Utility.Issues.Select(i => i as GitIgnoreException).FirstOrDefault(i => i != null);
|
||||
if (gitIgnoreException != null)
|
||||
{
|
||||
Styles.Warning(string.Format(GitIgnoreExceptionWarning, gitIgnoreException.Exception));
|
||||
Styles.Warning(String.Format(GitIgnoreExceptionWarning, gitIgnoreException.Exception));
|
||||
}
|
||||
|
||||
foreach (GitIgnoreIssue issue in Utility.Issues.Select(i => i as GitIgnoreIssue).Where(i => i != null))
|
||||
foreach (var issue in Utility.Issues.Select(i => i as GitIgnoreIssue).Where(i => i != null))
|
||||
{
|
||||
if (string.IsNullOrEmpty(issue.Line))
|
||||
{
|
||||
Styles.Warning(string.Format(GitIgnoreIssueNoLineWarning, issue.File, issue.Description));
|
||||
Styles.Warning(String.Format(GitIgnoreIssueNoLineWarning, issue.File, issue.Description));
|
||||
}
|
||||
else
|
||||
{
|
||||
Styles.Warning(string.Format(GitIgnoreIssueWarning, issue.File, issue.Line, issue.Description));
|
||||
Styles.Warning(String.Format(GitIgnoreIssueWarning, issue.File, issue.Line, issue.Description));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void OnRemotesGUI()
|
||||
private void OnRemotesGUI()
|
||||
{
|
||||
float remotesWith = position.width - Styles.RemotesTotalHorizontalMargin - 16f;
|
||||
float
|
||||
nameWidth = remotesWith * Styles.RemotesNameRatio,
|
||||
userWidth = remotesWith * Styles.RemotesUserRatio,
|
||||
hostWidth = remotesWith * Styles.RemotesHostRation,
|
||||
accessWidth = remotesWith * Styles.RemotesAccessRatio;
|
||||
var remotesWith = position.width - Styles.RemotesTotalHorizontalMargin - 16f;
|
||||
var nameWidth = remotesWith * Styles.RemotesNameRatio;
|
||||
var userWidth = remotesWith * Styles.RemotesUserRatio;
|
||||
var hostWidth = remotesWith * Styles.RemotesHostRation;
|
||||
var accessWidth = remotesWith * Styles.RemotesAccessRatio;
|
||||
|
||||
GUILayout.Label(RemotesTitle, EditorStyles.boldLabel);
|
||||
GUILayout.BeginVertical(GUI.skin.box);
|
||||
{
|
||||
GUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
{
|
||||
TableCell(RemoteNameTitle, nameWidth);
|
||||
TableCell(RemoteUserTitle, userWidth);
|
||||
TableCell(RemoteHostTitle, hostWidth);
|
||||
TableCell(RemoteAccessTitle, accessWidth);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
for (int index = 0; index < remotes.Count; ++index)
|
||||
for (var index = 0; index < remotes.Count; ++index)
|
||||
{
|
||||
GitRemote remote = remotes[index];
|
||||
var remote = remotes[index];
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
TableCell(remote.Name, nameWidth);
|
||||
TableCell(remote.User, userWidth);
|
||||
TableCell(remote.Host, hostWidth);
|
||||
TableCell(remote.Function.ToString(), accessWidth);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
|
||||
void OnGitIgnoreRulesGUI()
|
||||
private void OnGitIgnoreRulesGUI()
|
||||
{
|
||||
float gitignoreRulesWith = position.width - Styles.GitIgnoreRulesTotalHorizontalMargin - Styles.GitIgnoreRulesSelectorWidth - 16f;
|
||||
float
|
||||
effectWidth = gitignoreRulesWith * Styles.GitIgnoreRulesEffectRatio,
|
||||
fileWidth = gitignoreRulesWith * Styles.GitIgnoreRulesFileRatio,
|
||||
lineWidth = gitignoreRulesWith * Styles.GitIgnoreRulesLineRatio;
|
||||
var gitignoreRulesWith = position.width - Styles.GitIgnoreRulesTotalHorizontalMargin - Styles.GitIgnoreRulesSelectorWidth - 16f;
|
||||
var effectWidth = gitignoreRulesWith * Styles.GitIgnoreRulesEffectRatio;
|
||||
var fileWidth = gitignoreRulesWith * Styles.GitIgnoreRulesFileRatio;
|
||||
var lineWidth = gitignoreRulesWith * Styles.GitIgnoreRulesLineRatio;
|
||||
|
||||
GUILayout.Label(GitIgnoreRulesTitle, EditorStyles.boldLabel);
|
||||
GUILayout.BeginVertical(GUI.skin.box);
|
||||
GUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
GUILayout.Space(Styles.GitIgnoreRulesSelectorWidth);
|
||||
TableCell(GitIgnoreRulesEffect, effectWidth);
|
||||
TableCell(GitIgnoreRulesFile, fileWidth);
|
||||
TableCell(GitIgnoreRulesLine, lineWidth);
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
{
|
||||
GUILayout.Space(Styles.GitIgnoreRulesSelectorWidth);
|
||||
TableCell(GitIgnoreRulesEffect, effectWidth);
|
||||
TableCell(GitIgnoreRulesFile, fileWidth);
|
||||
TableCell(GitIgnoreRulesLine, lineWidth);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
int count = GitIgnoreRule.Count;
|
||||
for (int index = 0; index < count; ++index)
|
||||
var count = GitIgnoreRule.Count;
|
||||
for (var index = 0; index < count; ++index)
|
||||
{
|
||||
GitIgnoreRule rule;
|
||||
if (GitIgnoreRule.TryLoad(index, out rule))
|
||||
{
|
||||
GitIgnoreRule rule;
|
||||
if (GitIgnoreRule.TryLoad(index, out rule))
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Space(Styles.GitIgnoreRulesSelectorWidth);
|
||||
GUILayout.Space(Styles.GitIgnoreRulesSelectorWidth);
|
||||
|
||||
if (gitIgnoreRulesSelection == index && Event.current.type == EventType.Repaint)
|
||||
{
|
||||
Rect selectorRect = GUILayoutUtility.GetLastRect();
|
||||
selectorRect.Set(selectorRect.x, selectorRect.y + 2f, selectorRect.width - 2f, EditorGUIUtility.singleLineHeight);
|
||||
EditorStyles.foldout.Draw(selectorRect, false, false, false, false);
|
||||
}
|
||||
|
||||
TableCell(rule.Effect.ToString(), effectWidth);
|
||||
// TODO: Tint if the regex is null
|
||||
TableCell(rule.FileString, fileWidth);
|
||||
TableCell(rule.LineString, lineWidth);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (Event.current.type == EventType.MouseDown && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition))
|
||||
if (gitIgnoreRulesSelection == index && Event.current.type == EventType.Repaint)
|
||||
{
|
||||
newGitIgnoreRulesSelection = index;
|
||||
Event.current.Use();
|
||||
var selectorRect = GUILayoutUtility.GetLastRect();
|
||||
selectorRect.Set(selectorRect.x, selectorRect.y + 2f, selectorRect.width - 2f, EditorGUIUtility.singleLineHeight);
|
||||
EditorStyles.foldout.Draw(selectorRect, false, false, false, false);
|
||||
}
|
||||
|
||||
TableCell(rule.Effect.ToString(), effectWidth);
|
||||
// TODO: Tint if the regex is null
|
||||
TableCell(rule.FileString, fileWidth);
|
||||
TableCell(rule.LineString, lineWidth);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (Event.current.type == EventType.MouseDown && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition))
|
||||
{
|
||||
newGitIgnoreRulesSelection = index;
|
||||
Event.current.Use();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button(NewGitIgnoreRuleButton, EditorStyles.miniButton))
|
||||
{
|
||||
GitIgnoreRule.New();
|
||||
GUIUtility.hotControl = GUIUtility.keyboardControl = -1;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(EditorGUIUtility.standardVerticalSpacing);
|
||||
|
||||
// Selected gitignore rule edit
|
||||
|
||||
GitIgnoreRule selectedRule;
|
||||
if (GitIgnoreRule.TryLoad(gitIgnoreRulesSelection, out selectedRule))
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button(NewGitIgnoreRuleButton, EditorStyles.miniButton))
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box);
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button(DeleteGitIgnoreRuleButton, EditorStyles.miniButton))
|
||||
{
|
||||
GitIgnoreRule.Delete(gitIgnoreRulesSelection);
|
||||
newGitIgnoreRulesSelection = gitIgnoreRulesSelection - 1;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
GitIgnoreRuleEffect newEffect = (GitIgnoreRuleEffect)EditorGUILayout.EnumPopup(GitIgnoreRulesEffect, selectedRule.Effect);
|
||||
string newFile = EditorGUILayout.TextField(GitIgnoreRulesFile, selectedRule.FileString);
|
||||
string newLine = EditorGUILayout.TextField(GitIgnoreRulesLine, selectedRule.LineString);
|
||||
GUILayout.Label(GitIgnoreRulesDescription);
|
||||
string newDescription = EditorGUILayout.TextArea(selectedRule.TriggerText, Styles.CommitDescriptionFieldStyle);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
GitIgnoreRule.Save(gitIgnoreRulesSelection, newEffect, newFile, newLine, newDescription);
|
||||
EvaluateProjectConfigurationTask.Schedule();
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
GitIgnoreRule.New();
|
||||
GUIUtility.hotControl = GUIUtility.keyboardControl = -1;
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(EditorGUIUtility.standardVerticalSpacing);
|
||||
|
||||
// Selected gitignore rule edit
|
||||
|
||||
GitIgnoreRule selectedRule;
|
||||
if (GitIgnoreRule.TryLoad(gitIgnoreRulesSelection, out selectedRule))
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box);
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button(DeleteGitIgnoreRuleButton, EditorStyles.miniButton))
|
||||
{
|
||||
GitIgnoreRule.Delete(gitIgnoreRulesSelection);
|
||||
newGitIgnoreRulesSelection = gitIgnoreRulesSelection - 1;
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var newEffect = (GitIgnoreRuleEffect)EditorGUILayout.EnumPopup(GitIgnoreRulesEffect, selectedRule.Effect);
|
||||
var newFile = EditorGUILayout.TextField(GitIgnoreRulesFile, selectedRule.FileString);
|
||||
var newLine = EditorGUILayout.TextField(GitIgnoreRulesLine, selectedRule.LineString);
|
||||
GUILayout.Label(GitIgnoreRulesDescription);
|
||||
var newDescription = EditorGUILayout.TextArea(selectedRule.TriggerText, Styles.CommitDescriptionFieldStyle);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
GitIgnoreRule.Save(gitIgnoreRulesSelection, newEffect, newFile, newLine, newDescription);
|
||||
EvaluateProjectConfigurationTask.Schedule();
|
||||
}
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
|
||||
static void TableCell(string label, float width)
|
||||
{
|
||||
GUILayout.Label(label, EditorStyles.miniLabel, GUILayout.Width(width), GUILayout.MaxWidth(width));
|
||||
}
|
||||
|
||||
|
||||
void OnInstallPathGUI()
|
||||
private void OnInstallPathGUI()
|
||||
{
|
||||
var gitInstallPath = Utility.GitInstallPath;
|
||||
// Install path field
|
||||
EditorGUI.BeginChangeCheck();
|
||||
string gitInstallPath = Utility.GitInstallPath;
|
||||
Styles.PathField(
|
||||
ref gitInstallPath,
|
||||
() => EditorUtility.OpenFilePanel(
|
||||
GitInstallBrowseTitle,
|
||||
Path.GetDirectoryName(FindGitTask.DefaultGitPath),
|
||||
Path.GetExtension(FindGitTask.DefaultGitPath)
|
||||
),
|
||||
ValidateGitInstall
|
||||
);
|
||||
{
|
||||
Styles.PathField(ref gitInstallPath,
|
||||
() =>
|
||||
EditorUtility.OpenFilePanel(GitInstallBrowseTitle, Path.GetDirectoryName(FindGitTask.DefaultGitPath),
|
||||
Path.GetExtension(FindGitTask.DefaultGitPath)), ValidateGitInstall);
|
||||
}
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Utility.GitInstallPath = gitInstallPath;
|
||||
|
@ -407,11 +443,11 @@ namespace GitHub.Unity
|
|||
GUILayout.Space(EditorGUIUtility.standardVerticalSpacing);
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
// Find button - for attempting to locate a new install
|
||||
if (GUILayout.Button(GitInstallFindButton, GUILayout.ExpandWidth(false)))
|
||||
{
|
||||
FindGitTask.Schedule(path =>
|
||||
{
|
||||
FindGitTask.Schedule(path => {
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
Utility.GitInstallPath = path;
|
||||
|
@ -426,52 +462,17 @@ namespace GitHub.Unity
|
|||
{
|
||||
Application.OpenURL(GitInstallURL);
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
|
||||
void ResetInitDirectory()
|
||||
private void ResetInitDirectory()
|
||||
{
|
||||
initDirectory = Utility.UnityProjectPath;
|
||||
GUIUtility.keyboardControl = GUIUtility.hotControl = 0;
|
||||
}
|
||||
|
||||
|
||||
static bool ValidateInitDirectory(string path)
|
||||
{
|
||||
if (Utility.UnityProjectPath.IndexOf(path) != 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog(
|
||||
InvalidInitDirectoryTitle,
|
||||
string.Format(InvalidInitDirectoryMessage, path),
|
||||
InvalidInitDirectoryOK
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool ValidateGitInstall(string path)
|
||||
{
|
||||
if (!FindGitTask.ValidateGitInstall(path))
|
||||
{
|
||||
EditorUtility.DisplayDialog(
|
||||
GitInstallPickInvalidTitle,
|
||||
string.Format(GitInstallPickInvalidMessage, path),
|
||||
GitInstallPickInvalidOK
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Init()
|
||||
private void Init()
|
||||
{
|
||||
Debug.LogFormat("TODO: Init '{0}'", initDirectory);
|
||||
}
|
||||
|
|
|
@ -1,45 +1,50 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
using Debug = System.Diagnostics.Debug;
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
interface IView
|
||||
{
|
||||
Rect position { get; }
|
||||
void Refresh();
|
||||
void Repaint();
|
||||
void OnGUI();
|
||||
Rect position { get; }
|
||||
}
|
||||
|
||||
|
||||
abstract class Subview : IView
|
||||
{
|
||||
const string NullParentError = "Subview parent is null";
|
||||
|
||||
private const string NullParentError = "Subview parent is null";
|
||||
|
||||
protected IView parent;
|
||||
|
||||
public virtual void Refresh()
|
||||
{}
|
||||
|
||||
public Rect position
|
||||
public abstract void OnGUI();
|
||||
|
||||
public void Repaint()
|
||||
{
|
||||
get
|
||||
{
|
||||
return parent.position;
|
||||
}
|
||||
parent.Repaint();
|
||||
}
|
||||
|
||||
|
||||
public void Show(IView parent)
|
||||
public void Show(IView parentView)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(parent != null, NullParentError);
|
||||
Debug.Assert(parentView != null, NullParentError);
|
||||
|
||||
this.parent = parent;
|
||||
parent = parentView;
|
||||
OnShow();
|
||||
}
|
||||
|
||||
public virtual void OnSelectionChange()
|
||||
{}
|
||||
|
||||
void OnEnable()
|
||||
protected virtual void OnShow()
|
||||
{}
|
||||
|
||||
protected virtual void OnHide()
|
||||
{}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (parent != null)
|
||||
{
|
||||
|
@ -47,25 +52,14 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void OnDisable()
|
||||
private void OnDisable()
|
||||
{
|
||||
OnHide();
|
||||
}
|
||||
|
||||
|
||||
protected virtual void OnShow() {}
|
||||
protected virtual void OnHide() {}
|
||||
|
||||
|
||||
public virtual void Refresh() {}
|
||||
public virtual void OnSelectionChange() {}
|
||||
public abstract void OnGUI();
|
||||
|
||||
|
||||
public void Repaint()
|
||||
public Rect position
|
||||
{
|
||||
parent.Repaint();
|
||||
get { return parent.position; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,161 +1,56 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Object = UnityEngine.Object;
|
||||
using System.Linq;
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Debug = System.Diagnostics.Debug;
|
||||
|
||||
namespace GitHub.Unity
|
||||
{
|
||||
[System.Serializable]
|
||||
[Serializable]
|
||||
class Window : EditorWindow, IView
|
||||
{
|
||||
private const float DefaultNotificationTimeout = 4f;
|
||||
private const string Title = "GitHub";
|
||||
private const string LaunchMenu = "Window/GitHub";
|
||||
private const string RefreshButton = "Refresh";
|
||||
private const string UnknownSubTabError = "Unsupported view mode: {0}";
|
||||
private const string BadNotificationDelayError = "A delay of {0} is shorter than the default delay and thus would get pre-empted.";
|
||||
private const string HistoryTitle = "History";
|
||||
private const string ChangesTitle = "Changes";
|
||||
private const string BranchesTitle = "Branches";
|
||||
private const string SettingsTitle = "Settings";
|
||||
|
||||
[NonSerialized] private double notificationClearTime = -1;
|
||||
|
||||
[SerializeField] private SubTab activeTab = SubTab.History;
|
||||
[SerializeField] private BranchesView branchesTab = new BranchesView();
|
||||
[SerializeField] private ChangesView changesTab = new ChangesView();
|
||||
[SerializeField] private HistoryView historyTab = new HistoryView();
|
||||
[SerializeField] private SettingsView settingsTab = new SettingsView();
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
RefreshRunner.Initialize();
|
||||
}
|
||||
|
||||
class RefreshRunner : AssetPostprocessor
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
Tasks.ScheduleMainThread(Refresh);
|
||||
}
|
||||
|
||||
|
||||
static void OnPostprocessAllAssets(string[] imported, string[] deleted, string[] moveDestination, string[] moveSource)
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
|
||||
static void Refresh()
|
||||
{
|
||||
Utility.UnregisterReadyCallback(OnReady);
|
||||
Utility.RegisterReadyCallback(OnReady);
|
||||
}
|
||||
|
||||
|
||||
static void OnReady()
|
||||
{
|
||||
foreach (Window window in Object.FindObjectsOfTypeAll(typeof(Window)))
|
||||
{
|
||||
window.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum SubTab
|
||||
{
|
||||
History,
|
||||
Changes,
|
||||
Branches,
|
||||
Settings
|
||||
}
|
||||
|
||||
|
||||
const float DefaultNotificationTimeout = 4f;
|
||||
const string
|
||||
Title = "GitHub",
|
||||
LaunchMenu = "Window/GitHub",
|
||||
RefreshButton = "Refresh",
|
||||
UnknownSubTabError = "Unsupported view mode: {0}",
|
||||
BadNotificationDelayError = "A delay of {0} is shorter than the default delay and thus would get pre-empted.",
|
||||
HistoryTitle = "History",
|
||||
ChangesTitle = "Changes",
|
||||
BranchesTitle = "Branches",
|
||||
SettingsTitle = "Settings";
|
||||
|
||||
|
||||
[MenuItem(LaunchMenu)]
|
||||
static void Launch()
|
||||
public static void Launch()
|
||||
{
|
||||
GetWindow<Window>().Show();
|
||||
}
|
||||
|
||||
|
||||
[SerializeField] SubTab activeTab = SubTab.History;
|
||||
[SerializeField] HistoryView historyTab = new HistoryView();
|
||||
[SerializeField] ChangesView changesTab = new ChangesView();
|
||||
[SerializeField] BranchesView branchesTab = new BranchesView();
|
||||
[SerializeField] SettingsView settingsTab = new SettingsView();
|
||||
|
||||
|
||||
double notificationClearTime = -1;
|
||||
|
||||
|
||||
public HistoryView HistoryTab { get { return historyTab; } }
|
||||
public ChangesView ChangesTab { get { return changesTab; } }
|
||||
public BranchesView BranchesTab { get { return branchesTab; } }
|
||||
public SettingsView SettingsTab { get { return settingsTab; } }
|
||||
|
||||
|
||||
Subview ActiveTab
|
||||
{
|
||||
get
|
||||
{
|
||||
switch(activeTab)
|
||||
{
|
||||
case SubTab.History:
|
||||
return historyTab;
|
||||
case SubTab.Changes:
|
||||
return changesTab;
|
||||
case SubTab.Branches:
|
||||
return branchesTab;
|
||||
case SubTab.Settings:
|
||||
return settingsTab;
|
||||
default:
|
||||
throw new ArgumentException(string.Format(UnknownSubTabError, activeTab));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
historyTab.Show(this);
|
||||
changesTab.Show(this);
|
||||
branchesTab.Show(this);
|
||||
settingsTab.Show(this);
|
||||
|
||||
Utility.UnregisterReadyCallback(Refresh);
|
||||
Utility.RegisterReadyCallback(Refresh);
|
||||
}
|
||||
|
||||
|
||||
new public void ShowNotification(GUIContent content)
|
||||
{
|
||||
ShowNotification(content, DefaultNotificationTimeout);
|
||||
}
|
||||
|
||||
|
||||
public void ShowNotification(GUIContent content, float timeout)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(timeout <= DefaultNotificationTimeout, string.Format(BadNotificationDelayError, timeout));
|
||||
|
||||
notificationClearTime = timeout < DefaultNotificationTimeout ? EditorApplication.timeSinceStartup + timeout : -1f;
|
||||
base.ShowNotification(content);
|
||||
}
|
||||
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
// Set window title
|
||||
titleContent = new GUIContent(Title, Styles.TitleIcon);
|
||||
|
||||
ProjectSettingsIssue settingsIssues = Utility.Issues.Select(i => i as ProjectSettingsIssue).FirstOrDefault(i => i != null);
|
||||
var settingsIssues = Utility.Issues.Select(i => i as ProjectSettingsIssue).FirstOrDefault(i => i != null);
|
||||
|
||||
// Initial state
|
||||
if (
|
||||
!Utility.ActiveRepository ||
|
||||
!Utility.GitFound ||
|
||||
(settingsIssues != null && (
|
||||
settingsIssues.WasCaught(ProjectSettingsEvaluation.EditorSettingsMissing) ||
|
||||
settingsIssues.WasCaught(ProjectSettingsEvaluation.BadVCSSettings))
|
||||
)
|
||||
)
|
||||
if (!Utility.ActiveRepository || !Utility.GitFound ||
|
||||
(settingsIssues != null &&
|
||||
(settingsIssues.WasCaught(ProjectSettingsEvaluation.EditorSettingsMissing) ||
|
||||
settingsIssues.WasCaught(ProjectSettingsEvaluation.BadVCSSettings))))
|
||||
{
|
||||
activeTab = SubTab.Settings; // If we do complete init, make sure that we return to the settings tab for further setup
|
||||
settingsTab.OnGUI();
|
||||
|
@ -164,42 +59,27 @@ namespace GitHub.Unity
|
|||
|
||||
// Subtabs & toolbar
|
||||
GUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
{
|
||||
TabButton(ref activeTab, SubTab.History, HistoryTitle);
|
||||
TabButton(ref activeTab, SubTab.Changes, ChangesTitle);
|
||||
TabButton(ref activeTab, SubTab.Branches, BranchesTitle);
|
||||
TabButton(ref activeTab, SubTab.Settings, SettingsTitle);
|
||||
}
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// GUI for the active tab
|
||||
ActiveTab.OnGUI();
|
||||
}
|
||||
|
||||
|
||||
void Update()
|
||||
{
|
||||
// Notification auto-clear timer override
|
||||
if (notificationClearTime > 0f && EditorApplication.timeSinceStartup > notificationClearTime)
|
||||
{
|
||||
notificationClearTime = -1f;
|
||||
RemoveNotification();
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void TabButton(ref SubTab activeTab, SubTab tab, string title)
|
||||
{
|
||||
activeTab = GUILayout.Toggle(activeTab == tab, title, EditorStyles.toolbarButton) ? tab : activeTab;
|
||||
}
|
||||
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
EvaluateProjectConfigurationTask.Schedule();
|
||||
|
@ -210,10 +90,124 @@ namespace GitHub.Unity
|
|||
}
|
||||
}
|
||||
|
||||
public new void ShowNotification(GUIContent content)
|
||||
{
|
||||
ShowNotification(content, DefaultNotificationTimeout);
|
||||
}
|
||||
|
||||
void OnSelectionChange()
|
||||
public void ShowNotification(GUIContent content, float timeout)
|
||||
{
|
||||
Debug.Assert(timeout <= DefaultNotificationTimeout, String.Format(BadNotificationDelayError, timeout));
|
||||
|
||||
notificationClearTime = timeout < DefaultNotificationTimeout ? EditorApplication.timeSinceStartup + timeout : -1f;
|
||||
base.ShowNotification(content);
|
||||
}
|
||||
|
||||
private static void TabButton(ref SubTab activeTab, SubTab tab, string title)
|
||||
{
|
||||
activeTab = GUILayout.Toggle(activeTab == tab, title, EditorStyles.toolbarButton) ? tab : activeTab;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
historyTab.Show(this);
|
||||
changesTab.Show(this);
|
||||
branchesTab.Show(this);
|
||||
settingsTab.Show(this);
|
||||
|
||||
Utility.UnregisterReadyCallback(Refresh);
|
||||
Utility.RegisterReadyCallback(Refresh);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Notification auto-clear timer override
|
||||
if (notificationClearTime > 0f && EditorApplication.timeSinceStartup > notificationClearTime)
|
||||
{
|
||||
notificationClearTime = -1f;
|
||||
RemoveNotification();
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectionChange()
|
||||
{
|
||||
ActiveTab.OnSelectionChange();
|
||||
}
|
||||
|
||||
public HistoryView HistoryTab
|
||||
{
|
||||
get { return historyTab; }
|
||||
}
|
||||
|
||||
public ChangesView ChangesTab
|
||||
{
|
||||
get { return changesTab; }
|
||||
}
|
||||
|
||||
public BranchesView BranchesTab
|
||||
{
|
||||
get { return branchesTab; }
|
||||
}
|
||||
|
||||
public SettingsView SettingsTab
|
||||
{
|
||||
get { return settingsTab; }
|
||||
}
|
||||
|
||||
private Subview ActiveTab
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (activeTab)
|
||||
{
|
||||
case SubTab.History:
|
||||
return historyTab;
|
||||
case SubTab.Changes:
|
||||
return changesTab;
|
||||
case SubTab.Branches:
|
||||
return branchesTab;
|
||||
case SubTab.Settings:
|
||||
return settingsTab;
|
||||
default:
|
||||
throw new ArgumentException(String.Format(UnknownSubTabError, activeTab));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RefreshRunner : AssetPostprocessor
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
Tasks.ScheduleMainThread(Refresh);
|
||||
}
|
||||
|
||||
private static void OnPostprocessAllAssets(string[] imported, string[] deleted, string[] moveDestination, string[] moveSource)
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private static void Refresh()
|
||||
{
|
||||
Utility.UnregisterReadyCallback(OnReady);
|
||||
Utility.RegisterReadyCallback(OnReady);
|
||||
}
|
||||
|
||||
private static void OnReady()
|
||||
{
|
||||
foreach (Window window in FindObjectsOfTypeAll(typeof(Window)))
|
||||
{
|
||||
window.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum SubTab
|
||||
{
|
||||
History,
|
||||
Changes,
|
||||
Branches,
|
||||
Settings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RuleSet Name="GitHub" Description="This ruleset only includes the rules we care about. I'll be adding new ones as we fix our codebase." ToolsVersion="14.0">
|
||||
<IncludeAll Action="Warning" />
|
||||
<Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed">
|
||||
<Rule Id="CA1000" Action="None" />
|
||||
<Rule Id="CA1002" Action="None" />
|
||||
<Rule Id="CA1006" Action="None" />
|
||||
<Rule Id="CA1014" Action="None" />
|
||||
<Rule Id="CA1017" Action="None" />
|
||||
<Rule Id="CA1020" Action="None" />
|
||||
<Rule Id="CA1021" Action="None" />
|
||||
<Rule Id="CA1024" Action="None" />
|
||||
<Rule Id="CA1026" Action="None" />
|
||||
<Rule Id="CA1030" Action="None" />
|
||||
<Rule Id="CA1031" Action="None" />
|
||||
<Rule Id="CA1033" Action="None" />
|
||||
<Rule Id="CA1034" Action="None" />
|
||||
<Rule Id="CA1045" Action="None" />
|
||||
<Rule Id="CA1051" Action="None" />
|
||||
<Rule Id="CA1054" Action="None" />
|
||||
<Rule Id="CA1055" Action="None" />
|
||||
<Rule Id="CA1056" Action="None" />
|
||||
<Rule Id="CA1062" Action="None" />
|
||||
<Rule Id="CA1063" Action="None" />
|
||||
<Rule Id="CA1303" Action="None" />
|
||||
<Rule Id="CA1305" Action="None" />
|
||||
<Rule Id="CA1307" Action="None" />
|
||||
<Rule Id="CA1401" Action="None" />
|
||||
<Rule Id="CA1402" Action="None" />
|
||||
<Rule Id="CA1403" Action="None" />
|
||||
<Rule Id="CA1405" Action="None" />
|
||||
<Rule Id="CA1406" Action="None" />
|
||||
<Rule Id="CA1407" Action="None" />
|
||||
<Rule Id="CA1408" Action="None" />
|
||||
<Rule Id="CA1409" Action="None" />
|
||||
<Rule Id="CA1410" Action="None" />
|
||||
<Rule Id="CA1411" Action="None" />
|
||||
<Rule Id="CA1412" Action="None" />
|
||||
<Rule Id="CA1413" Action="None" />
|
||||
<Rule Id="CA1501" Action="None" />
|
||||
<Rule Id="CA1502" Action="None" />
|
||||
<Rule Id="CA1506" Action="None" />
|
||||
<Rule Id="CA1701" Action="None" />
|
||||
<Rule Id="CA1702" Action="None" />
|
||||
<Rule Id="CA1703" Action="None" />
|
||||
<Rule Id="CA1704" Action="None" />
|
||||
<Rule Id="CA1707" Action="None" />
|
||||
<Rule Id="CA1708" Action="None" />
|
||||
<Rule Id="CA1709" Action="None" />
|
||||
<Rule Id="CA1710" Action="None" />
|
||||
<Rule Id="CA1711" Action="None" />
|
||||
<Rule Id="CA1712" Action="None" />
|
||||
<Rule Id="CA1713" Action="None" />
|
||||
<Rule Id="CA1714" Action="None" />
|
||||
<Rule Id="CA1715" Action="None" />
|
||||
<Rule Id="CA1716" Action="None" />
|
||||
<Rule Id="CA1717" Action="None" />
|
||||
<Rule Id="CA1719" Action="None" />
|
||||
<Rule Id="CA1720" Action="None" />
|
||||
<Rule Id="CA1721" Action="None" />
|
||||
<Rule Id="CA1722" Action="None" />
|
||||
<Rule Id="CA1724" Action="None" />
|
||||
<Rule Id="CA1725" Action="None" />
|
||||
<Rule Id="CA1726" Action="None" />
|
||||
<Rule Id="CA1800" Action="None" />
|
||||
<Rule Id="CA1801" Action="None" />
|
||||
<Rule Id="CA1810" Action="None" />
|
||||
<Rule Id="CA1811" Action="None" />
|
||||
<Rule Id="CA1812" Action="None" />
|
||||
<Rule Id="CA1822" Action="None" />
|
||||
<Rule Id="CA2000" Action="None" />
|
||||
<Rule Id="CA2118" Action="None" />
|
||||
<Rule Id="CA2122" Action="None" />
|
||||
<Rule Id="CA2200" Action="None" />
|
||||
<Rule Id="CA2201" Action="Warning" />
|
||||
<Rule Id="CA2204" Action="None" />
|
||||
<Rule Id="CA2210" Action="None" />
|
||||
<Rule Id="CA2211" Action="None" />
|
||||
<Rule Id="CA2213" Action="None" />
|
||||
<Rule Id="CA2227" Action="None" />
|
||||
<Rule Id="CA2235" Action="None" />
|
||||
<Rule Id="IDE001" Action="None" />
|
||||
</Rules>
|
||||
<Rules AnalyzerId="Microsoft.CodeAnalysis.CSharp.Features" RuleNamespace="Microsoft.CodeAnalysis.CSharp.Features">
|
||||
<Rule Id="IDE0001" Action="None" />
|
||||
</Rules>
|
||||
</RuleSet>
|
Загрузка…
Ссылка в новой задаче