From afec12d40756006c20fc9dcac08bce52c6d82d8c Mon Sep 17 00:00:00 2001 From: Sam Christiansen Date: Thu, 8 Nov 2018 15:25:10 -0800 Subject: [PATCH 1/8] File History --- src/GitHub.Api/Application/ApiClient.cs | 4 +- src/GitHub.Api/Application/IApiClient.cs | 2 +- src/GitHub.Api/Application/Organization.cs | 4 +- src/GitHub.Api/Git/GitClient.cs | 34 +++ src/GitHub.Api/Git/Tasks/GitCheckoutTask.cs | 23 +- src/GitHub.Api/Git/Tasks/GitLogTask.cs | 16 +- .../Editor/GitHub.Unity/UI/BaseWindow.cs | 2 +- .../Editor/GitHub.Unity/UI/ContextMenu.cs | 36 +++ .../GitHub.Unity/UI/FileHistoryWindow.cs | 244 ++++++++++++++++++ .../Editor/GitHub.Unity/UI/HistoryView.cs | 2 +- .../Editor/GitHub.Unity/UI/PopupWindow.cs | 2 +- 11 files changed, 359 insertions(+), 10 deletions(-) create mode 100644 src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ContextMenu.cs create mode 100644 src/UnityExtension/Assets/Editor/GitHub.Unity/UI/FileHistoryWindow.cs diff --git a/src/GitHub.Api/Application/ApiClient.cs b/src/GitHub.Api/Application/ApiClient.cs index 01d189def..51d13fe38 100644 --- a/src/GitHub.Api/Application/ApiClient.cs +++ b/src/GitHub.Api/Application/ApiClient.cs @@ -283,13 +283,13 @@ private GitHubUser GetValidatedGitHubUser(Connection keychainConnection, IKeycha } } - class GitHubUser + public class GitHubUser { public string Name { get; set; } public string Login { get; set; } } - class GitHubRepository + public class GitHubRepository { public string Name { get; set; } public string CloneUrl { get; set; } diff --git a/src/GitHub.Api/Application/IApiClient.cs b/src/GitHub.Api/Application/IApiClient.cs index 650595ce2..1bd5fc070 100644 --- a/src/GitHub.Api/Application/IApiClient.cs +++ b/src/GitHub.Api/Application/IApiClient.cs @@ -2,7 +2,7 @@ namespace GitHub.Unity { - interface IApiClient + public interface IApiClient { HostAddress HostAddress { get; } UriString OriginalUrl { get; } diff --git a/src/GitHub.Api/Application/Organization.cs b/src/GitHub.Api/Application/Organization.cs index e78849dd6..8deea7d99 100644 --- a/src/GitHub.Api/Application/Organization.cs +++ b/src/GitHub.Api/Application/Organization.cs @@ -1,8 +1,8 @@ namespace GitHub.Unity { - class Organization + public class Organization { public string Name { get; set; } public string Login { get; set; } } -} \ No newline at end of file +} diff --git a/src/GitHub.Api/Git/GitClient.cs b/src/GitHub.Api/Git/GitClient.cs index 5e454f6fa..d0e97787c 100644 --- a/src/GitHub.Api/Git/GitClient.cs +++ b/src/GitHub.Api/Git/GitClient.cs @@ -199,6 +199,15 @@ public interface IGitClient /// String output of git command ITask DiscardAll(IOutputProcessor processor = null); + /// + /// Executes at least one `git checkout` command to checkout files at the given changeset + /// + /// The md5 of the changeset + /// The files to check out + /// A custom output processor instance + /// String output of git command + ITask CheckoutVersion(string changeset, IEnumerable files, IOutputProcessor processor = null); + /// /// Executes at least one `git reset HEAD` command to remove files from the git index. /// @@ -241,6 +250,13 @@ public interface IGitClient /// of output ITask> Log(BaseOutputListProcessor processor = null); + /// + /// Executes `git log -- ` to get the history of a specific file. + /// + /// A custom output processor instance + /// of output + ITask> LogFile(NPath file, BaseOutputListProcessor processor = null); + /// /// Executes `git --version` to get the git version. /// @@ -332,6 +348,17 @@ public ITask> Log(BaseOutputListProcessor process .Then((success, list) => success ? list : new List()); } + /// + public ITask> LogFile(NPath file, BaseOutputListProcessor processor = null) + { + return new GitLogTask(file, new GitObjectFactory(environment), cancellationToken, processor) + .Configure(processManager) + .Catch(exception => exception is ProcessException && + exception.Message.StartsWith("fatal: your current branch") && + exception.Message.EndsWith("does not have any commits yet")) + .Then((success, list) => success ? list : new List()); + } + /// public ITask Version(IOutputProcessor processor = null) { @@ -549,6 +576,13 @@ public ITask DiscardAll(IOutputProcessor processor = null) .Configure(processManager); } + /// + public ITask CheckoutVersion(string changeset, IEnumerable files, IOutputProcessor processor = null) + { + return new GitCheckoutTask(changeset, files, cancellationToken, processor) + .Configure(processManager); + } + /// public ITask Remove(IList files, IOutputProcessor processor = null) diff --git a/src/GitHub.Api/Git/Tasks/GitCheckoutTask.cs b/src/GitHub.Api/Git/Tasks/GitCheckoutTask.cs index ea2e9f4d3..401281e22 100644 --- a/src/GitHub.Api/Git/Tasks/GitCheckoutTask.cs +++ b/src/GitHub.Api/Git/Tasks/GitCheckoutTask.cs @@ -30,8 +30,29 @@ public GitCheckoutTask(CancellationToken token, arguments = "checkout -- ."; } + public GitCheckoutTask( + string changeset, + IEnumerable files, + CancellationToken token, + IOutputProcessor processor = null) : base(token, processor ?? new SimpleOutputProcessor()) + { + Guard.ArgumentNotNull(files, "files"); + Name = TaskName; + + arguments = "checkout "; + arguments += changeset; + arguments += " -- "; + + foreach (var file in files) + { + arguments += " \"" + file.ToNPath().ToString(SlashMode.Forward) + "\""; + } + + Message = "Checking out files at rev " + changeset.Substring(0, 7); + } + public override string ProcessArguments { get { return arguments; } } public override TaskAffinity Affinity { get { return TaskAffinity.Exclusive; } } - public override string Message { get; set; } = "Checking out branch..."; + public override string Message { get; set; } = "Checking out files..."; } } \ No newline at end of file diff --git a/src/GitHub.Api/Git/Tasks/GitLogTask.cs b/src/GitHub.Api/Git/Tasks/GitLogTask.cs index 955521a61..a55e72e5c 100644 --- a/src/GitHub.Api/Git/Tasks/GitLogTask.cs +++ b/src/GitHub.Api/Git/Tasks/GitLogTask.cs @@ -5,17 +5,31 @@ namespace GitHub.Unity class GitLogTask : ProcessTaskWithListOutput { private const string TaskName = "git log"; + private const string baseArguments = @"-c i18n.logoutputencoding=utf8 -c core.quotepath=false log --pretty=format:""%H%n%P%n%aN%n%aE%n%aI%n%cN%n%cE%n%cI%n%B---GHUBODYEND---"" --name-status"; + private readonly string arguments; public GitLogTask(IGitObjectFactory gitObjectFactory, CancellationToken token, BaseOutputListProcessor processor = null) : base(token, processor ?? new LogEntryOutputProcessor(gitObjectFactory)) { Name = TaskName; + arguments = baseArguments; + } + + public GitLogTask(NPath file, + IGitObjectFactory gitObjectFactory, + CancellationToken token, BaseOutputListProcessor processor = null) + : base(token, processor ?? new LogEntryOutputProcessor(gitObjectFactory)) + { + Name = TaskName; + arguments = baseArguments; + arguments += " -- "; + arguments += " \"" + file.ToString(SlashMode.Forward) + "\""; } public override string ProcessArguments { - get { return @"-c i18n.logoutputencoding=utf8 -c core.quotepath=false log --pretty=format:""%H%n%P%n%aN%n%aE%n%aI%n%cN%n%cE%n%cI%n%B---GHUBODYEND---"" --name-status"; } + get { return arguments; } } public override string Message { get; set; } = "Loading the history..."; } diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BaseWindow.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BaseWindow.cs index fa21bebf3..134af3c2a 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BaseWindow.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BaseWindow.cs @@ -7,7 +7,7 @@ namespace GitHub.Unity { - abstract class BaseWindow : EditorWindow, IView + public abstract class BaseWindow : EditorWindow, IView { [NonSerialized] private bool initialized = false; [NonSerialized] private IUser cachedUser; diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ContextMenu.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ContextMenu.cs new file mode 100644 index 000000000..95872fb45 --- /dev/null +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ContextMenu.cs @@ -0,0 +1,36 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +namespace GitHub.Unity +{ + public class ContextMenu + { + [MenuItem("Assets/Git/History", false)] + private static void GitFileHistory() + { + if (Selection.assetGUIDs != null) + { + int maxWindowsToOpen = 10; + int windowsOpened = 0; + foreach(var guid in Selection.assetGUIDs) + { + var assetPath = AssetDatabase.GUIDToAssetPath(guid); + FileHistoryWindow.OpenWindow(assetPath); + windowsOpened++; + if (windowsOpened >= maxWindowsToOpen) + { + break; + } + } + } + } + + [MenuItem("Assets/Git/History", true)] + private static bool GitFileHistoryValidation() + { + return Selection.assetGUIDs != null && Selection.assetGUIDs.Length > 0; + } + } +} \ No newline at end of file diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/FileHistoryWindow.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/FileHistoryWindow.cs new file mode 100644 index 000000000..bf4d6989f --- /dev/null +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/FileHistoryWindow.cs @@ -0,0 +1,244 @@ +using System.Collections.Generic; +using System.Linq; +using System; +using UnityEngine; +using UnityEditor; + +namespace GitHub.Unity +{ + public class FileHistoryWindow : BaseWindow + { + [SerializeField] private string assetPath; + [SerializeField] private List history; + [SerializeField] private Vector2 scroll; + [SerializeField] private Vector2 detailsScroll; + [NonSerialized] private bool busy; + [SerializeField] private HistoryControl historyControl; + [SerializeField] private GitLogEntry selectedEntry = GitLogEntry.Default; + [SerializeField] private ChangesTree treeChanges = new ChangesTree { IsSelectable = false, DisplayRootNode = false }; + + public static FileHistoryWindow OpenWindow(string assetPath) + { + var popupWindow = CreateInstance(); + + popupWindow.titleContent = new GUIContent(assetPath + " History"); + popupWindow.Open(assetPath); + + popupWindow.Show(); + + return popupWindow; + } + + public override bool IsBusy { get { return this.busy; } } + + public void Open(string assetPath) + { + this.assetPath = assetPath; + + this.RefreshLog(); + } + + public void RefreshLog() + { + var path = Application.dataPath.ToNPath().Parent.Combine(assetPath.ToNPath()); + this.busy = true; + this.GitClient.LogFile(path).ThenInUI((success, logEntries) => { + this.history = logEntries; + this.BuildHistoryControl(); + this.Repaint(); + this.busy = false; + }).Start(); + } + + private void CheckoutVersion(string commitID) + { + this.busy = true; + this.GitClient.CheckoutVersion(commitID, new string[]{assetPath}).ThenInUI((success, result) => { + AssetDatabase.Refresh(); + this.busy = false; + }).Start(); + } + + private void Checkout() + { + // TODO: This is a destructive, irreversible operation; we should prompt user if + // there are any changes to the file + this.CheckoutVersion(this.selectedEntry.CommitID); + } + + public override void OnUI() + { + // TODO: + // - should handle case where the file is outside of the repository (handle exceptional cases) + // - should display a spinner while history is still loading... + base.OnUI(); + GUILayout.BeginHorizontal(Styles.HeaderStyle); + { + GUILayout.Label("GIT File History for: ", Styles.BoldLabel); + if (HyperlinkLabel(this.assetPath)) + { + var asset = AssetDatabase.LoadMainAssetAtPath(this.assetPath); + Selection.activeObject = asset; + EditorGUIUtility.PingObject(asset); + } + GUILayout.FlexibleSpace(); + } + GUILayout.EndHorizontal(); + + if (historyControl != null) + { + var rect = GUILayoutUtility.GetLastRect(); + var historyControlRect = new Rect(0f, 0f, Position.width, Position.height - rect.height); + + var requiresRepaint = historyControl.Render(historyControlRect, + entry => { + selectedEntry = entry; + BuildTree(); + }, + entry => { }, entry => { + GenericMenu menu = new GenericMenu(); + menu.AddItem(new GUIContent("Checkout version " + entry.ShortID), false, Checkout); + menu.ShowAsContext(); + }); + + if (requiresRepaint) + Redraw(); + } + + // DrawDetails is maybe irrelevant? Would be a nice place to put the short id perhaps? + DrawDetails(); + } + + private bool HyperlinkLabel(string label) + { + bool returnValue = false; + if (GUILayout.Button(label, HyperlinkStyle)) + { + returnValue = true; + } + var rect = GUILayoutUtility.GetLastRect(); + var size = HyperlinkStyle.CalcSize(new GUIContent(label)); + rect.width = size.x; + EditorGUIUtility.AddCursorRect(rect, MouseCursor.Link); + return returnValue; + } + + private void BuildHistoryControl() + { + if (historyControl == null) + { + historyControl = new HistoryControl(); + } + + historyControl.Load(0, this.history); + } + + private const string CommitDetailsTitle = "Commit details"; + private const string ClearSelectionButton = "×"; + + private void DrawDetails() + { + if (!selectedEntry.Equals(GitLogEntry.Default)) + { + // Top bar for scrolling to selection or clearing it + GUILayout.BeginHorizontal(EditorStyles.toolbar); + { + if (GUILayout.Button(CommitDetailsTitle, Styles.ToolbarButtonStyle)) + { + historyControl.ScrollTo(historyControl.SelectedIndex); + } + if (GUILayout.Button(ClearSelectionButton, Styles.ToolbarButtonStyle, GUILayout.ExpandWidth(false))) + { + selectedEntry = GitLogEntry.Default; + historyControl.SelectedIndex = -1; + } + } + GUILayout.EndHorizontal(); + + // Log entry details - including changeset tree (if any changes are found) + detailsScroll = GUILayout.BeginScrollView(detailsScroll, GUILayout.Height(250)); + { + HistoryDetailsEntry(selectedEntry); + + GUILayout.Space(EditorGUIUtility.standardVerticalSpacing); + GUILayout.Label("Files changed", EditorStyles.boldLabel); + GUILayout.Space(-5); + + var rect = GUILayoutUtility.GetLastRect(); + GUILayout.BeginHorizontal(Styles.HistoryFileTreeBoxStyle); + GUILayout.BeginVertical(); + { + var borderLeft = Styles.Label.margin.left; + var treeControlRect = new Rect(rect.x + borderLeft, rect.y, Position.width - borderLeft * 2, Position.height - rect.height + Styles.CommitAreaPadding); + var treeRect = new Rect(0f, 0f, 0f, 0f); + if (treeChanges != null) + { + treeChanges.FolderStyle = Styles.Foldout; + treeChanges.TreeNodeStyle = Styles.TreeNode; + treeChanges.ActiveTreeNodeStyle = Styles.ActiveTreeNode; + treeChanges.FocusedTreeNodeStyle = Styles.FocusedTreeNode; + treeChanges.FocusedActiveTreeNodeStyle = Styles.FocusedActiveTreeNode; + + treeRect = treeChanges.Render(treeControlRect, detailsScroll, + node => { + }, + node => { + }, + node => { + }); + + if (treeChanges.RequiresRepaint) + Redraw(); + } + + GUILayout.Space(treeRect.y - treeControlRect.y); + } + GUILayout.EndVertical(); + GUILayout.EndHorizontal(); + + GUILayout.Space(EditorGUIUtility.standardVerticalSpacing); + } + GUILayout.EndScrollView(); + } + } + + private void HistoryDetailsEntry(GitLogEntry entry) + { + GUILayout.BeginVertical(Styles.HeaderBoxStyle); + GUILayout.Label(entry.Summary, Styles.HistoryDetailsTitleStyle); + + GUILayout.Space(-5); + + GUILayout.BeginHorizontal(); + GUILayout.Label(entry.PrettyTimeString, Styles.HistoryDetailsMetaInfoStyle); + GUILayout.Label(entry.AuthorName, Styles.HistoryDetailsMetaInfoStyle); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + GUILayout.Space(3); + GUILayout.EndVertical(); + } + + private void BuildTree() + { + treeChanges.PathSeparator = Environment.FileSystem.DirectorySeparatorChar.ToString(); + treeChanges.Load(selectedEntry.changes.Select(entry => new GitStatusEntryTreeData(entry))); + Redraw(); + } + + protected static GUIStyle hyperlinkStyle = null; + + public static GUIStyle HyperlinkStyle + { + get + { + if (hyperlinkStyle == null) + { + hyperlinkStyle = new GUIStyle(EditorStyles.wordWrappedLabel); + hyperlinkStyle.normal.textColor = new Color(95.0f/255.0f, 170.0f/255.0f, 247.0f/255.0f); + } + return hyperlinkStyle; + } + } + } +} \ No newline at end of file diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs index 69b42e83e..f35423133 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs @@ -7,7 +7,7 @@ namespace GitHub.Unity { [Serializable] - class HistoryControl + public class HistoryControl { private const string HistoryEntryDetailFormat = "{0} {1}"; diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PopupWindow.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PopupWindow.cs index d7da7cfbe..85fea7a02 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PopupWindow.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PopupWindow.cs @@ -5,7 +5,7 @@ namespace GitHub.Unity { [Serializable] - class PopupWindow : BaseWindow + public class PopupWindow : BaseWindow { public enum PopupViewType { From f9f5202f716a46d8887a457618f540726710afe8 Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Wed, 2 Jan 2019 08:48:55 -0500 Subject: [PATCH 2/8] Fixing build error --- src/GitHub.Api/Application/ApiClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GitHub.Api/Application/ApiClient.cs b/src/GitHub.Api/Application/ApiClient.cs index a81a6f152..161540e41 100644 --- a/src/GitHub.Api/Application/ApiClient.cs +++ b/src/GitHub.Api/Application/ApiClient.cs @@ -469,14 +469,14 @@ private GitHubUser GetValidatedGitHubUser() } } - class GitHubHostMeta + public class GitHubHostMeta { public bool VerifiablePasswordAuthentication { get; set; } public string GithubServicesSha { get; set; } public string InstalledVersion { get; set; } } - class GitHubUser + public class GitHubUser { public string Name { get; set; } public string Login { get; set; } From f5bab7a101f73ef12c6f468503397788818c348e Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Wed, 2 Jan 2019 08:49:03 -0500 Subject: [PATCH 3/8] Nit pick text --- src/GitHub.Api/Git/Tasks/GitCheckoutTask.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/GitHub.Api/Git/Tasks/GitCheckoutTask.cs b/src/GitHub.Api/Git/Tasks/GitCheckoutTask.cs index 401281e22..d1d050b52 100644 --- a/src/GitHub.Api/Git/Tasks/GitCheckoutTask.cs +++ b/src/GitHub.Api/Git/Tasks/GitCheckoutTask.cs @@ -48,11 +48,11 @@ public GitCheckoutTask( arguments += " \"" + file.ToNPath().ToString(SlashMode.Forward) + "\""; } - Message = "Checking out files at rev " + changeset.Substring(0, 7); - } + Message = "Checking out files at rev " + changeset.Substring(0, 7); + } public override string ProcessArguments { get { return arguments; } } public override TaskAffinity Affinity { get { return TaskAffinity.Exclusive; } } public override string Message { get; set; } = "Checking out files..."; } -} \ No newline at end of file +} From 29b5142949a9a34a353d81533e89f89d32961f02 Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Wed, 2 Jan 2019 10:09:07 -0500 Subject: [PATCH 4/8] Fixing project reference --- .../Assets/Editor/GitHub.Unity/GitHub.Unity.45.csproj | 2 ++ .../Assets/Editor/GitHub.Unity/GitHub.Unity.csproj | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.45.csproj b/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.45.csproj index 7cf5bb05b..1bedc423f 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.45.csproj +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.45.csproj @@ -90,6 +90,8 @@ + + diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.csproj b/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.csproj index ae6f39583..28ca2c0d4 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.csproj +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.csproj @@ -77,6 +77,8 @@ + + From dd5218d4ef41884582e2cbe2272470e10efc264e Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Mon, 14 Jan 2019 09:34:23 -0500 Subject: [PATCH 5/8] Adding a context menu --- .../Editor/GitHub.Unity/UI/ContextMenu.cs | 2 +- .../Editor/GitHub.Unity/UI/HistoryView.cs | 23 ++++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ContextMenu.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ContextMenu.cs index 95872fb45..2ca5eb783 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ContextMenu.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ContextMenu.cs @@ -33,4 +33,4 @@ private static bool GitFileHistoryValidation() return Selection.assetGUIDs != null && Selection.assetGUIDs.Length > 0; } } -} \ No newline at end of file +} diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs index f35423133..afef175e8 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs @@ -320,7 +320,7 @@ class HistoryView : Subview [SerializeField] private int statusAhead; - [SerializeField] private ChangesTree treeChanges = new ChangesTree { IsSelectable = false, DisplayRootNode = false }; + [SerializeField] private ChangesTree treeChanges = new ChangesTree { DisplayRootNode = false }; [SerializeField] private CacheUpdateEvent lastLogChangedEvent; [SerializeField] private CacheUpdateEvent lastTrackingStatusChangedEvent; @@ -435,11 +435,13 @@ public override void OnGUI() treeChanges.FocusedActiveTreeNodeStyle = Styles.FocusedActiveTreeNode; treeRect = treeChanges.Render(treeControlRect, detailsScroll, - node => { }, - node => { - }, - node => { - }); + singleClick: node => { }, + doubleClick: node => { }, + rightClick: node => { + var menu = CreateChangesTreeContextMenu(node); + menu.ShowAsContext(); + } + ); if (treeChanges.RequiresRepaint) Redraw(); @@ -588,5 +590,14 @@ private void BuildTree() treeChanges.Load(selectedEntry.changes.Select(entry => new GitStatusEntryTreeData(entry))); Redraw(); } + + private GenericMenu CreateChangesTreeContextMenu(ChangesTreeNode node) + { + var genericMenu = new GenericMenu(); + + genericMenu.AddItem(new GUIContent("Show History"), false, () => { }); + + return genericMenu; + } } } From 5f566c4c1a77c54c94bccbcadfd2ed2e1a2b2860 Mon Sep 17 00:00:00 2001 From: Sam Christiansen Date: Mon, 14 Jan 2019 12:16:07 -0800 Subject: [PATCH 6/8] Prefer IList vs IEnumerable --- src/GitHub.Api/Git/GitClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GitHub.Api/Git/GitClient.cs b/src/GitHub.Api/Git/GitClient.cs index 6facb03aa..6b77866cb 100644 --- a/src/GitHub.Api/Git/GitClient.cs +++ b/src/GitHub.Api/Git/GitClient.cs @@ -215,7 +215,7 @@ public interface IGitClient /// The files to check out /// A custom output processor instance /// String output of git command - ITask CheckoutVersion(string changeset, IEnumerable files, IOutputProcessor processor = null); + ITask CheckoutVersion(string changeset, IList files, IOutputProcessor processor = null); /// /// Executes at least one `git reset HEAD` command to remove files from the git index. @@ -593,7 +593,7 @@ public ITask DiscardAll(IOutputProcessor processor = null) } /// - public ITask CheckoutVersion(string changeset, IEnumerable files, IOutputProcessor processor = null) + public ITask CheckoutVersion(string changeset, IList files, IOutputProcessor processor = null) { return new GitCheckoutTask(changeset, files, cancellationToken, processor) .Configure(processManager); From e7aad4917b93d4f183aa2c8c9f1a6118bbefab8b Mon Sep 17 00:00:00 2001 From: Sam Christiansen Date: Thu, 17 Jan 2019 18:00:36 -0800 Subject: [PATCH 7/8] fix asmdef files giving errors on 2018.3 --- .../ExtensionLoader/ExtensionLoader.asmdef | 2 +- .../Assets/Editor/UnityTests/UnityTests.asmdef | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/ExtensionLoader/ExtensionLoader.asmdef b/src/UnityExtension/Assets/Editor/GitHub.Unity/ExtensionLoader/ExtensionLoader.asmdef index a5ed02083..1703a7af8 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/ExtensionLoader/ExtensionLoader.asmdef +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/ExtensionLoader/ExtensionLoader.asmdef @@ -1,6 +1,6 @@ { "name": "ExtensionLoader", - "references": ["../../build/GitHub.UnityShim.dll"], + "precompiledReferences": ["../../build/GitHub.UnityShim.dll"], "includePlatforms": [ "Editor" ], diff --git a/src/UnityExtension/Assets/Editor/UnityTests/UnityTests.asmdef b/src/UnityExtension/Assets/Editor/UnityTests/UnityTests.asmdef index eef024216..d49630a1a 100644 --- a/src/UnityExtension/Assets/Editor/UnityTests/UnityTests.asmdef +++ b/src/UnityExtension/Assets/Editor/UnityTests/UnityTests.asmdef @@ -3,8 +3,16 @@ "references": [ "GitHub.Unity" ], + "optionalUnityReferences": [ + "TestAssemblies" + ], "includePlatforms": [ "Editor" ], - "excludePlatforms": [] + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] } \ No newline at end of file From 389905c41c94bf8d16bf794349cf73b5e1212a1c Mon Sep 17 00:00:00 2001 From: Sam Christiansen Date: Fri, 18 Jan 2019 17:42:32 -0800 Subject: [PATCH 8/8] Overwrite confirmation and bug fixes History window will prompt to overwrite a file if the local contents have changed. Also fixed a couple bugs where the window would cause exceptions after a domain reload. --- .../GitHub.Unity/UI/FileHistoryWindow.cs | 98 ++++++++++++++----- 1 file changed, 72 insertions(+), 26 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/FileHistoryWindow.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/FileHistoryWindow.cs index bf4d6989f..ee0bf05a3 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/FileHistoryWindow.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/FileHistoryWindow.cs @@ -15,7 +15,14 @@ public class FileHistoryWindow : BaseWindow [NonSerialized] private bool busy; [SerializeField] private HistoryControl historyControl; [SerializeField] private GitLogEntry selectedEntry = GitLogEntry.Default; - [SerializeField] private ChangesTree treeChanges = new ChangesTree { IsSelectable = false, DisplayRootNode = false }; + [NonSerialized] private ChangesTree treeChanges; + + + private const string ConfirmCheckoutTitle = "Discard Changes?"; + private const string ConfirmCheckoutMessage = "There are modifications to file '{0}'; checking out a historical version will permanently overwite those changes. Continue?"; + private const string ConfirmCheckoutOK = "Overwrite"; + private const string ConfirmCheckoutCancel = "Cancel"; + public static FileHistoryWindow OpenWindow(string assetPath) { @@ -31,6 +38,14 @@ public static FileHistoryWindow OpenWindow(string assetPath) public override bool IsBusy { get { return this.busy; } } + private NPath FullFilePath + { + get + { + return Application.dataPath.ToNPath().Parent.Combine(assetPath.ToNPath()); + } + } + public void Open(string assetPath) { this.assetPath = assetPath; @@ -40,9 +55,8 @@ public void Open(string assetPath) public void RefreshLog() { - var path = Application.dataPath.ToNPath().Parent.Combine(assetPath.ToNPath()); this.busy = true; - this.GitClient.LogFile(path).ThenInUI((success, logEntries) => { + this.GitClient.LogFile(this.FullFilePath).ThenInUI((success, logEntries) => { this.history = logEntries; this.BuildHistoryControl(); this.Repaint(); @@ -50,10 +64,11 @@ public void RefreshLog() }).Start(); } + private void CheckoutVersion(string commitID) { this.busy = true; - this.GitClient.CheckoutVersion(commitID, new string[]{assetPath}).ThenInUI((success, result) => { + this.GitClient.CheckoutVersion(commitID, new string[]{FullFilePath}).ThenInUI((success, result) => { AssetDatabase.Refresh(); this.busy = false; }).Start(); @@ -61,9 +76,31 @@ private void CheckoutVersion(string commitID) private void Checkout() { - // TODO: This is a destructive, irreversible operation; we should prompt user if - // there are any changes to the file - this.CheckoutVersion(this.selectedEntry.CommitID); + GitClient.Status() + .ThenInUI((success, status) => + { + if (success) + { + bool promptUser = false; + + foreach (var entry in status.Entries) + { + if (entry.FullPath == this.FullFilePath) { + // local changes; prompt user before we checkout. + promptUser = true; + } + } + + if (!promptUser || EditorUtility.DisplayDialog(ConfirmCheckoutTitle, string.Format(ConfirmCheckoutMessage, this.assetPath), ConfirmCheckoutOK, ConfirmCheckoutCancel)) { + this.CheckoutVersion(this.selectedEntry.CommitID); + } + } + else + { + Debug.LogError("Error retrieving current repo status"); + } + }) + .Start(); } public override void OnUI() @@ -72,6 +109,7 @@ public override void OnUI() // - should handle case where the file is outside of the repository (handle exceptional cases) // - should display a spinner while history is still loading... base.OnUI(); + GUILayout.BeginHorizontal(Styles.HeaderStyle); { GUILayout.Label("GIT File History for: ", Styles.BoldLabel); @@ -133,9 +171,11 @@ private void BuildHistoryControl() historyControl.Load(0, this.history); } + private const string CommitDetailsTitle = "Commit details"; private const string ClearSelectionButton = "×"; + private void DrawDetails() { if (!selectedEntry.Equals(GitLogEntry.Default)) @@ -171,26 +211,28 @@ private void DrawDetails() var borderLeft = Styles.Label.margin.left; var treeControlRect = new Rect(rect.x + borderLeft, rect.y, Position.width - borderLeft * 2, Position.height - rect.height + Styles.CommitAreaPadding); var treeRect = new Rect(0f, 0f, 0f, 0f); - if (treeChanges != null) - { - treeChanges.FolderStyle = Styles.Foldout; - treeChanges.TreeNodeStyle = Styles.TreeNode; - treeChanges.ActiveTreeNodeStyle = Styles.ActiveTreeNode; - treeChanges.FocusedTreeNodeStyle = Styles.FocusedTreeNode; - treeChanges.FocusedActiveTreeNodeStyle = Styles.FocusedActiveTreeNode; - - treeRect = treeChanges.Render(treeControlRect, detailsScroll, - node => { - }, - node => { - }, - node => { - }); - - if (treeChanges.RequiresRepaint) - Redraw(); + + if (treeChanges == null) { // Can be null in the case of domain reloads + BuildTree(); } + treeChanges.FolderStyle = Styles.Foldout; + treeChanges.TreeNodeStyle = Styles.TreeNode; + treeChanges.ActiveTreeNodeStyle = Styles.ActiveTreeNode; + treeChanges.FocusedTreeNodeStyle = Styles.FocusedTreeNode; + treeChanges.FocusedActiveTreeNodeStyle = Styles.FocusedActiveTreeNode; + + treeRect = treeChanges.Render(treeControlRect, detailsScroll, + node => { + }, + node => { + }, + node => { + }); + + if (treeChanges.RequiresRepaint) + Redraw(); + GUILayout.Space(treeRect.y - treeControlRect.y); } GUILayout.EndVertical(); @@ -221,7 +263,11 @@ private void HistoryDetailsEntry(GitLogEntry entry) private void BuildTree() { - treeChanges.PathSeparator = Environment.FileSystem.DirectorySeparatorChar.ToString(); + treeChanges = new ChangesTree { + IsSelectable = false, + DisplayRootNode = false, + PathSeparator = Environment.FileSystem.DirectorySeparatorChar.ToString(), + }; treeChanges.Load(selectedEntry.changes.Select(entry => new GitStatusEntryTreeData(entry))); Redraw(); }