diff --git a/src/GitHub.Api/Application/ApiClient.cs b/src/GitHub.Api/Application/ApiClient.cs index 3ef20f9ce..161540e41 100644 --- a/src/GitHub.Api/Application/ApiClient.cs +++ b/src/GitHub.Api/Application/ApiClient.cs @@ -469,20 +469,20 @@ 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; } } - 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 83bfa17c9..09c79611f 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; } void CreateRepository(string name, string description, bool isPrivate, 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/Cache/CacheContainer.cs b/src/GitHub.Api/Cache/CacheContainer.cs index 40f1802fb..2c07ff867 100644 --- a/src/GitHub.Api/Cache/CacheContainer.cs +++ b/src/GitHub.Api/Cache/CacheContainer.cs @@ -90,6 +90,7 @@ public void Dispose() public IBranchCache BranchCache { get { return (IBranchCache)caches[CacheType.Branches].Value; } } public IGitLogCache GitLogCache { get { return (IGitLogCache)caches[CacheType.GitLog].Value; } } + public IGitFileLogCache GitFileLogCache { get { return (IGitFileLogCache)caches[CacheType.GitFileLog].Value; } } public IGitAheadBehindCache GitTrackingStatusCache { get { return (IGitAheadBehindCache)caches[CacheType.GitAheadBehind].Value; } } public IGitStatusCache GitStatusEntriesCache { get { return (IGitStatusCache)caches[CacheType.GitStatus].Value; } } public IGitLocksCache GitLocksCache { get { return (IGitLocksCache)caches[CacheType.GitLocks].Value; } } diff --git a/src/GitHub.Api/Cache/CacheInterfaces.cs b/src/GitHub.Api/Cache/CacheInterfaces.cs index be6bab1f1..ae815beac 100644 --- a/src/GitHub.Api/Cache/CacheInterfaces.cs +++ b/src/GitHub.Api/Cache/CacheInterfaces.cs @@ -9,6 +9,7 @@ public enum CacheType RepositoryInfo, Branches, GitLog, + GitFileLog, GitAheadBehind, GitStatus, GitLocks, @@ -22,6 +23,7 @@ public interface ICacheContainer : IDisposable IBranchCache BranchCache { get; } IGitLogCache GitLogCache { get; } + IGitFileLogCache GitFileLogCache { get; } IGitAheadBehindCache GitTrackingStatusCache { get; } IGitStatusCache GitStatusEntriesCache { get; } IGitLocksCache GitLocksCache { get; } @@ -115,6 +117,11 @@ public interface IGitLogCache : IManagedCache List Log { get; set; } } + public interface IGitFileLogCache : IManagedCache + { + GitFileLog FileLog { get; set; } + } + public interface ICanUpdate { void UpdateData(T data); diff --git a/src/GitHub.Api/Git/GitClient.cs b/src/GitHub.Api/Git/GitClient.cs index a59d4d37b..ecb9ec1e0 100644 --- a/src/GitHub.Api/Git/GitClient.cs +++ b/src/GitHub.Api/Git/GitClient.cs @@ -208,6 +208,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, IList files, IOutputProcessor processor = null); + /// /// Executes at least one `git reset HEAD` command to remove files from the git index. /// @@ -250,6 +259,14 @@ 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(string file, BaseOutputListProcessor processor = null); + /// /// Executes `git --version` to get the git version. /// @@ -341,6 +358,22 @@ public ITask> Log(BaseOutputListProcessor process .Then((success, list) => success ? list : new List()); } + /// + public ITask> LogFile(string file, BaseOutputListProcessor processor = null) + { + if (file == NPath.Default) + { + return new FuncTask>(cancellationToken, () => new List(0)); + } + + 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) { @@ -565,6 +598,13 @@ public ITask DiscardAll(IOutputProcessor processor = null) .Configure(processManager); } + /// + public ITask CheckoutVersion(string changeset, IList 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/GitFileLog.cs b/src/GitHub.Api/Git/GitFileLog.cs new file mode 100644 index 000000000..6795d6c4d --- /dev/null +++ b/src/GitHub.Api/Git/GitFileLog.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; + +namespace GitHub.Unity +{ + [Serializable] + public struct GitFileLog + { + public static GitFileLog Default = new GitFileLog(null, new List(0)); + + public string path; + public List logEntries; + + public GitFileLog(string path, List logEntries) + { + this.path = path; + this.logEntries = logEntries; + } + + public string Path + { + get { return path; } + set { path = value; } + } + + public List LogEntries + { + get { return logEntries; } + set { logEntries = value; } + } + } +} diff --git a/src/GitHub.Api/Git/IRepository.cs b/src/GitHub.Api/Git/IRepository.cs index 148015282..7bd579d37 100644 --- a/src/GitHub.Api/Git/IRepository.cs +++ b/src/GitHub.Api/Git/IRepository.cs @@ -21,6 +21,7 @@ public interface IRepository : IEquatable, IDisposable, IBackedByCa ITask RequestLock(NPath file); ITask ReleaseLock(NPath file, bool force); ITask DiscardChanges(GitStatusEntry[] discardEntries); + ITask CheckoutVersion(string changeset, IList files); /// /// Gets the name of the repository. @@ -61,8 +62,10 @@ public interface IRepository : IEquatable, IDisposable, IBackedByCa List CurrentLog { get; } bool IsBusy { get; } string CurrentHead { get; } + GitFileLog CurrentFileLog { get; } event Action LogChanged; + event Action FileLogChanged; event Action TrackingStatusChanged; event Action StatusEntriesChanged; event Action CurrentBranchChanged; @@ -78,7 +81,8 @@ public interface IRepository : IEquatable, IDisposable, IBackedByCa ITask DeleteBranch(string branch, bool force); ITask CreateBranch(string branch, string baseBranch); ITask SwitchBranch(string branch); + ITask UpdateFileLog(string path); void Refresh(CacheType cacheType); event Action OnProgress; } -} \ No newline at end of file +} diff --git a/src/GitHub.Api/Git/Repository.cs b/src/GitHub.Api/Git/Repository.cs index 9cceea0bc..e4b2a3ddd 100644 --- a/src/GitHub.Api/Git/Repository.cs +++ b/src/GitHub.Api/Git/Repository.cs @@ -25,8 +25,10 @@ sealed class Repository : IEquatable, IRepository private HashSet cacheInvalidationRequests = new HashSet(); private Dictionary> cacheUpdateEvents; private ProgressReporter progressReporter = new ProgressReporter(); + private string lastFileLog; public event Action LogChanged; + public event Action FileLogChanged; public event Action TrackingStatusChanged; public event Action StatusEntriesChanged; public event Action CurrentBranchChanged; @@ -63,6 +65,7 @@ public Repository(NPath localPath, ICacheContainer container) { CacheType.GitAheadBehind, c => TrackingStatusChanged?.Invoke(c) }, { CacheType.GitLocks, c => LocksChanged?.Invoke(c) }, { CacheType.GitLog, c => LogChanged?.Invoke(c) }, + { CacheType.GitFileLog, c => FileLogChanged?.Invoke(c) }, { CacheType.GitStatus, c => StatusEntriesChanged?.Invoke(c) }, { CacheType.GitUser, cacheUpdateEvent => { } }, { CacheType.RepositoryInfo, cacheUpdateEvent => { @@ -91,6 +94,7 @@ public void Initialize(IRepositoryManager theRepositoryManager, ITaskManager the this.repositoryManager.GitStatusUpdated += RepositoryManagerOnGitStatusUpdated; this.repositoryManager.GitAheadBehindStatusUpdated += RepositoryManagerOnGitAheadBehindStatusUpdated; this.repositoryManager.GitLogUpdated += RepositoryManagerOnGitLogUpdated; + this.repositoryManager.GitFileLogUpdated += RepositoryManagerOnGitFileLogUpdated; this.repositoryManager.GitLocksUpdated += RepositoryManagerOnGitLocksUpdated; this.repositoryManager.LocalBranchesUpdated += RepositoryManagerOnLocalBranchesUpdated; this.repositoryManager.RemoteBranchesUpdated += RepositoryManagerOnRemoteBranchesUpdated; @@ -138,11 +142,17 @@ public ITask SetupRemote(string remote, string remoteUrl) public ITask RequestLock(NPath file) => repositoryManager.LockFile(file); public ITask ReleaseLock(NPath file, bool force) => repositoryManager.UnlockFile(file, force); public ITask DiscardChanges(GitStatusEntry[] gitStatusEntry) => repositoryManager.DiscardChanges(gitStatusEntry); + public ITask CheckoutVersion(string changeset, IList files) => repositoryManager.CheckoutVersion(changeset, files); public ITask RemoteAdd(string remote, string url) => repositoryManager.RemoteAdd(remote, url); public ITask RemoteRemove(string remote) => repositoryManager.RemoteRemove(remote); public ITask DeleteBranch(string branch, bool force) => repositoryManager.DeleteBranch(branch, force); public ITask CreateBranch(string branch, string baseBranch) => repositoryManager.CreateBranch(branch, baseBranch); public ITask SwitchBranch(string branch) => repositoryManager.SwitchBranch(branch); + public ITask UpdateFileLog(string path) + { + lastFileLog = path; + return repositoryManager.UpdateFileLog(path); + } public void CheckAndRaiseEventsIfCacheNewer(CacheType cacheType, CacheUpdateEvent cacheUpdateEvent) => cacheContainer.CheckAndRaiseEventsIfCacheNewer(cacheType, cacheUpdateEvent); @@ -215,6 +225,10 @@ private void CacheHasBeenInvalidated(CacheType cacheType) repositoryManager?.UpdateGitLog().Catch(ex => InvalidationFailed(ex, cacheType)).Start(); break; + case CacheType.GitFileLog: + repositoryManager?.UpdateFileLog(lastFileLog).Catch(ex => InvalidationFailed(ex, cacheType)).Start(); + break; + case CacheType.GitAheadBehind: repositoryManager?.UpdateGitAheadBehindStatus().Catch(ex => InvalidationFailed(ex, cacheType)).Start(); break; @@ -295,6 +309,11 @@ private void RepositoryManagerOnGitLogUpdated(List gitLogEntries) taskManager.RunInUI(() => cacheContainer.GitLogCache.Log = gitLogEntries); } + private void RepositoryManagerOnGitFileLogUpdated(GitFileLog gitFileLog) + { + taskManager.RunInUI(() => cacheContainer.GitFileLogCache.FileLog = gitFileLog); + } + private void RepositoryManagerOnGitLocksUpdated(List gitLocks) { taskManager.RunInUI(() => cacheContainer.GitLocksCache.GitLocks = gitLocks); @@ -360,6 +379,7 @@ public void Dispose() public string CurrentBranchName => CurrentConfigBranch?.Name; public GitRemote? CurrentRemote => cacheContainer.RepositoryInfoCache.CurrentGitRemote; public List CurrentLog => cacheContainer.GitLogCache.Log; + public GitFileLog CurrentFileLog => cacheContainer.GitFileLogCache.FileLog; public List CurrentLocks => cacheContainer.GitLocksCache.GitLocks; public string CurrentHead => cacheContainer.RepositoryInfoCache.CurrentHead; diff --git a/src/GitHub.Api/Git/RepositoryManager.cs b/src/GitHub.Api/Git/RepositoryManager.cs index d84db5db6..d764fa010 100644 --- a/src/GitHub.Api/Git/RepositoryManager.cs +++ b/src/GitHub.Api/Git/RepositoryManager.cs @@ -13,6 +13,7 @@ public interface IRepositoryManager : IDisposable event Action GitStatusUpdated; event Action> GitLocksUpdated; event Action> GitLogUpdated; + event Action GitFileLogUpdated; event Action> LocalBranchesUpdated; event Action, Dictionary>> RemoteBranchesUpdated; event Action GitAheadBehindStatusUpdated; @@ -37,12 +38,15 @@ public interface IRepositoryManager : IDisposable ITask LockFile(NPath file); ITask UnlockFile(NPath file, bool force); ITask DiscardChanges(GitStatusEntry[] gitStatusEntries); + ITask CheckoutVersion(string changeset, IList files); ITask UpdateGitLog(); ITask UpdateGitStatus(); ITask UpdateGitAheadBehindStatus(); ITask UpdateLocks(); ITask UpdateRepositoryInfo(); ITask UpdateBranches(); + ITask UpdateFileLog(string path); + int WaitForEvents(); @@ -136,6 +140,7 @@ class RepositoryManager : IRepositoryManager public event Action GitAheadBehindStatusUpdated; public event Action> GitLocksUpdated; public event Action> GitLogUpdated; + public event Action GitFileLogUpdated; public event Action> LocalBranchesUpdated; public event Action, Dictionary>> RemoteBranchesUpdated; @@ -341,6 +346,13 @@ public ITask DiscardChanges(GitStatusEntry[] gitStatusEntries) return HookupHandlers(task, true); } + public ITask CheckoutVersion(string changeset, IList files) + { + var task = GitClient.CheckoutVersion(changeset, files) + .Then(() => DataNeedsRefreshing?.Invoke(CacheType.GitStatus)); + return HookupHandlers(task, false); + } + public ITask UpdateGitLog() { var task = GitClient.Log() @@ -354,6 +366,20 @@ public ITask UpdateGitLog() return HookupHandlers(task, false); } + public ITask UpdateFileLog(string path) + { + var task = GitClient.LogFile(path) + .Then((success, logEntries) => + { + if (success) + { + var gitFileLog = new GitFileLog(path, logEntries); + GitFileLogUpdated?.Invoke(gitFileLog); + } + }); + return HookupHandlers(task, false); + } + public ITask UpdateGitStatus() { var task = GitClient.Status() @@ -644,6 +670,7 @@ private void Dispose(bool disposing) GitStatusUpdated = null; GitAheadBehindStatusUpdated = null; GitLogUpdated = null; + GitFileLogUpdated = null; GitLocksUpdated = null; LocalBranchesUpdated = null; RemoteBranchesUpdated = null; diff --git a/src/GitHub.Api/Git/Tasks/GitCheckoutTask.cs b/src/GitHub.Api/Git/Tasks/GitCheckoutTask.cs index ea2e9f4d3..d1d050b52 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..e5d29dfab 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(string file, + IGitObjectFactory gitObjectFactory, + CancellationToken token, BaseOutputListProcessor processor = null) + : base(token, processor ?? new LogEntryOutputProcessor(gitObjectFactory)) + { + Name = TaskName; + arguments = baseArguments; + arguments += " -- "; + arguments += " \"" + file + "\""; } 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/GitHub.Api/GitHub.Api.45.csproj b/src/GitHub.Api/GitHub.Api.45.csproj index 6d3d8d514..7a322039a 100644 --- a/src/GitHub.Api/GitHub.Api.45.csproj +++ b/src/GitHub.Api/GitHub.Api.45.csproj @@ -79,6 +79,7 @@ + diff --git a/src/GitHub.Api/GitHub.Api.csproj b/src/GitHub.Api/GitHub.Api.csproj index 5b9441180..b7d31aea2 100644 --- a/src/GitHub.Api/GitHub.Api.csproj +++ b/src/GitHub.Api/GitHub.Api.csproj @@ -90,6 +90,7 @@ + diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/ApplicationCache.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/ApplicationCache.cs index a1fca0391..272566cf7 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/ApplicationCache.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/ApplicationCache.cs @@ -147,6 +147,7 @@ public IEnvironment Environment cacheContainer.SetCacheInitializer(CacheType.GitAheadBehind, () => GitAheadBehindCache.Instance); cacheContainer.SetCacheInitializer(CacheType.GitLocks, () => GitLocksCache.Instance); cacheContainer.SetCacheInitializer(CacheType.GitLog, () => GitLogCache.Instance); + cacheContainer.SetCacheInitializer(CacheType.GitFileLog, () => GitFileLogCache.Instance); cacheContainer.SetCacheInitializer(CacheType.GitStatus, () => GitStatusCache.Instance); cacheContainer.SetCacheInitializer(CacheType.GitUser, () => GitUserCache.Instance); cacheContainer.SetCacheInitializer(CacheType.RepositoryInfo, () => RepositoryInfoCache.Instance); @@ -622,6 +623,46 @@ public List Log public override TimeSpan DataTimeout { get { return TimeSpan.FromMinutes(1); } } } + [Location("cache/gitfilelog.yaml", LocationAttribute.Location.LibraryFolder)] + sealed class GitFileLogCache : ManagedCacheBase, IGitFileLogCache + { + [SerializeField] private GitFileLog fileLog = GitFileLog.Default; + + public GitFileLogCache() : base(CacheType.GitFileLog) + { } + + public GitFileLog FileLog + { + get + { + ValidateData(); + return fileLog; + } + set + { + var now = DateTimeOffset.Now; + var isUpdated = false; + + var shouldUpdate = forcedInvalidation; + + if (!shouldUpdate) + { + shouldUpdate = true; + } + + if (shouldUpdate) + { + fileLog = value; + isUpdated = true; + } + + SaveData(now, isUpdated); + } + } + + public override TimeSpan DataTimeout { get { return TimeSpan.FromMinutes(1); } } + } + [Location("cache/gittrackingstatus.yaml", LocationAttribute.Location.LibraryFolder)] sealed class GitAheadBehindCache : ManagedCacheBase, IGitAheadBehindCache { 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 @@ + + diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Styles.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Styles.cs index 5a72b41ad..2fbeaf7c3 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Styles.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Styles.cs @@ -65,6 +65,7 @@ class Styles headerBranchLabelStyle, headerUrlLabelStyle, headerRepoLabelStyle, + fileHistoryLogTitleStyle, headerTitleStyle, headerDescriptionStyle, toolbarButtonStyle, @@ -472,14 +473,14 @@ public static GUIStyle ToolbarButtonStyle } } - public static GUIStyle HistoryLockStyle + public static GUIStyle LockButtonStyle { get { if (historyLockStyle == null) { historyLockStyle = new GUIStyle(GUI.skin.FindStyle("IN LockButton")); - historyLockStyle.name = "HistoryLockStyle"; + historyLockStyle.name = "LockStyle"; } historyLockStyle.margin = new RectOffset(3, 3, 2, 2); return historyLockStyle; @@ -734,8 +735,7 @@ public static GUIStyle BoldCenteredLabel return boldCenteredLabel; } } - - + public static GUIStyle CommitDescriptionFieldStyle { get @@ -802,6 +802,20 @@ public static GUIStyle HyperlinkStyle } } + public static GUIStyle FileHistoryLogTitleStyle + { + get + { + if (fileHistoryLogTitleStyle == null) + { + fileHistoryLogTitleStyle = new GUIStyle(EditorStyles.largeLabel); + fileHistoryLogTitleStyle.name = "FileHistoryLogTitleStyle"; + fileHistoryLogTitleStyle.margin = new RectOffset(0, 0, 0, 0); + } + return fileHistoryLogTitleStyle; + } + } + public static Texture2D ActiveBranchIcon { get diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BaseWindow.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BaseWindow.cs index 10d72469d..e0fe85c84 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..6b88821d6 --- /dev/null +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ContextMenu.cs @@ -0,0 +1,12 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +namespace GitHub.Unity +{ + public class ContextMenu + { + + } +} 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..6979c58dc --- /dev/null +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/FileHistoryWindow.cs @@ -0,0 +1,250 @@ +using System; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace GitHub.Unity +{ + public class FileHistoryWindow : BaseWindow + { + [MenuItem("Assets/Git/History", false)] + private static void GitFileHistory() + { + if (Selection.assetGUIDs != null) + { + var assetPath = + AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs.First()) + .ToNPath(); + + var windowType = typeof(Window); + var fileHistoryWindow = GetWindow(windowType); + fileHistoryWindow.InitializeWindow(EntryPoint.ApplicationManager); + fileHistoryWindow.SetSelectedPath(assetPath); + fileHistoryWindow.Show(); + } + } + + [MenuItem("Assets/Git/History", true)] + private static bool GitFileHistoryValidation() + { + return Selection.assetGUIDs != null && Selection.assetGUIDs.Length > 0; + } + + private const string Title = "File History"; + + [NonSerialized] private bool firstOnGUI = true; + [NonSerialized] private Texture selectedIcon; + + [SerializeField] private bool locked; + [SerializeField] private FileHistoryView fileHistoryView = new FileHistoryView(); + [SerializeField] private UnityEngine.Object selectedObject; + [SerializeField] private string selectedObjectAssetPath; + [SerializeField] private string selectedObjectRepositoryPath; + + public void SetSelectedPath(string assetPath) + { + selectedObject = null; + selectedObjectAssetPath = null; + selectedObjectRepositoryPath = null; + + if (selectedObjectAssetPath != NPath.Default) + { + selectedObjectAssetPath = assetPath; + selectedObject = AssetDatabase.LoadMainAssetAtPath(selectedObjectAssetPath); + + selectedObjectRepositoryPath = + Environment.GetRepositoryPath(assetPath.ToNPath()) + .ToString(SlashMode.Forward); + } + + LoadSelectedIcon(); + + Repository.UpdateFileLog(selectedObjectRepositoryPath) + .Start(); + } + + public override void Initialize(IApplicationManager applicationManager) + { + base.Initialize(applicationManager); + + fileHistoryView.InitializeView(this); + } + + public override bool IsBusy + { + get { return false; } + } + + public override void OnEnable() + { + base.OnEnable(); + + LoadSelectedIcon(); + + if (fileHistoryView != null) + fileHistoryView.OnEnable(); + } + + public override void OnDisable() + { + base.OnDisable(); + if (fileHistoryView != null) + fileHistoryView.OnDisable(); + } + + public override void OnDataUpdate() + { + base.OnDataUpdate(); + MaybeUpdateData(); + + if (fileHistoryView != null) + fileHistoryView.OnDataUpdate(); + } + + public override void OnRepositoryChanged(IRepository oldRepository) + { + base.OnRepositoryChanged(oldRepository); + + DetachHandlers(oldRepository); + AttachHandlers(Repository); + + if (HasRepository) + { + + } + else + { + + } + } + + public override void OnSelectionChange() + { + base.OnSelectionChange(); + if (fileHistoryView != null) + fileHistoryView.OnSelectionChange(); + + if (!locked) + { + selectedObject = Selection.activeObject; + + string assetPath = null; + if (selectedObject != null) + { + assetPath = AssetDatabase.GetAssetPath(selectedObject); + } + + SetSelectedPath(assetPath); + } + } + + public override void Refresh() + { + base.Refresh(); + if (fileHistoryView != null) + fileHistoryView.Refresh(); + Refresh(CacheType.GitFileLog); + Redraw(); + } + + public override void OnUI() + { + base.OnUI(); + + if (selectedObject != null) + { + GUILayout.BeginVertical(Styles.HeaderStyle); + { + DoHeaderGUI(); + + fileHistoryView.OnGUI(); + } + GUILayout.EndVertical(); + } + } + + private void MaybeUpdateData() + { + if (firstOnGUI) + { + titleContent = new GUIContent(Title, Styles.SmallLogo); + } + firstOnGUI = false; + } + + private void AttachHandlers(IRepository repository) + { + if (repository == null) + return; + } + + private void DetachHandlers(IRepository repository) + { + if (repository == null) + return; + } + + private void LoadSelectedIcon() + { + Texture nodeIcon = null; + + if (!string.IsNullOrEmpty(selectedObjectAssetPath)) + { + if (selectedObjectAssetPath.ToNPath().DirectoryExists()) + { + nodeIcon = Styles.FolderIcon; + } + else + { + nodeIcon = UnityEditorInternal.InternalEditorUtility.GetIconForFile(selectedObjectAssetPath); + } + + nodeIcon.hideFlags = HideFlags.HideAndDontSave; + } + + selectedIcon = nodeIcon; + } + + private void ShowButton(Rect rect) + { + EditorGUI.BeginChangeCheck(); + + locked = GUI.Toggle(rect, locked, GUIContent.none, Styles.LockButtonStyle); + + if (!EditorGUI.EndChangeCheck()) + return; + + this.OnSelectionChange(); + } + + private void DoHeaderGUI() + { + GUILayout.BeginHorizontal(Styles.HeaderBoxStyle); + { + var iconWidth = 32; + var iconHeight = 32; + + GUILayout.Label(selectedIcon, GUILayout.Height(iconWidth), GUILayout.Width(iconHeight)); + GUILayout.Space(16); + + GUILayout.BeginVertical(); + { + GUILayout.Label(selectedObjectAssetPath, Styles.FileHistoryLogTitleStyle); + + GUILayout.BeginHorizontal(); + { + GUILayout.FlexibleSpace(); + + if (GUILayout.Button("Show in Project")) + { + EditorGUIUtility.PingObject(selectedObject); + } + } + GUILayout.EndHorizontal(); + } + GUILayout.EndVertical(); + } + GUILayout.EndHorizontal(); + } + } +} diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs index 69b42e83e..c4b326c1e 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}"; @@ -302,90 +302,92 @@ public void ScrollTo(int index, float offset = 0f) } } - [Serializable] - class HistoryView : Subview + abstract class HistoryBase : Subview { - private const string CommitDetailsTitle = "Commit details"; - private const string ClearSelectionButton = "×"; - - [SerializeField] private bool currentLogHasUpdate; - [SerializeField] private bool currentTrackingStatusHasUpdate; + protected const string CommitDetailsTitle = "Commit details"; + protected const string ClearSelectionButton = "×"; - [SerializeField] private HistoryControl historyControl; - [SerializeField] private GitLogEntry selectedEntry = GitLogEntry.Default; - - [SerializeField] private Vector2 detailsScroll; - - [SerializeField] private List logEntries = new List(); - - [SerializeField] private int statusAhead; - - [SerializeField] private ChangesTree treeChanges = new ChangesTree { IsSelectable = false, DisplayRootNode = false }; - - [SerializeField] private CacheUpdateEvent lastLogChangedEvent; - [SerializeField] private CacheUpdateEvent lastTrackingStatusChangedEvent; + protected abstract HistoryControl HistoryControl { get; set; } + protected abstract GitLogEntry SelectedEntry { get; set; } + protected abstract ChangesTree TreeChanges { get; set; } + protected abstract Vector2 DetailsScroll { get; set; } - public override void OnEnable() + protected void BuildHistoryControl(int loadAhead, List gitLogEntries) { - base.OnEnable(); - - if (treeChanges != null) + if (HistoryControl == null) { - treeChanges.ViewHasFocus = HasFocus; - treeChanges.UpdateIcons(Styles.FolderIcon); + HistoryControl = new HistoryControl(); } - AttachHandlers(Repository); - ValidateCachedData(Repository); + HistoryControl.Load(loadAhead, gitLogEntries); + if (!SelectedEntry.Equals(GitLogEntry.Default) + && SelectedEntry.CommitID != HistoryControl.SelectedGitLogEntry.CommitID) + { + SelectedEntry = GitLogEntry.Default; + } } - public override void OnDisable() + protected void BuildTreeChanges() { - base.OnDisable(); - DetachHandlers(Repository); + TreeChanges.PathSeparator = Environment.FileSystem.DirectorySeparatorChar.ToString(); + TreeChanges.Load(SelectedEntry.changes.Select(entry => new GitStatusEntryTreeData(entry))); + Redraw(); } - public override void Refresh() + protected void RevertCommit() { - base.Refresh(); - Refresh(CacheType.GitLog); - Refresh(CacheType.GitAheadBehind); - } + var dialogTitle = "Revert commit"; + var dialogBody = string.Format(@"Are you sure you want to revert the following commit:""{0}""", SelectedEntry.Summary); - public override void OnDataUpdate() - { - base.OnDataUpdate(); - MaybeUpdateData(); + if (EditorUtility.DisplayDialog(dialogTitle, dialogBody, "Revert", "Cancel")) + { + Repository + .Revert(SelectedEntry.CommitID) + .FinallyInUI((success, e) => { + if (!success) + { + EditorUtility.DisplayDialog(dialogTitle, + "Error reverting commit: " + e.Message, Localization.Cancel); + } + AssetDatabase.Refresh(); + }) + .Start(); + } } - public override void OnFocusChanged() + protected void HistoryDetailsEntry(GitLogEntry entry) { - base.OnFocusChanged(); - var hasFocus = HasFocus; - if (treeChanges.ViewHasFocus != hasFocus) - { - treeChanges.ViewHasFocus = hasFocus; - Redraw(); - } + 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(); } - public override void OnGUI() + protected void DoHistoryGui(Rect rect, Action historyControlRightClick = null, + Action changesTreeRightClick = null) { - var rect = GUILayoutUtility.GetLastRect(); - if (historyControl != null) + if (HistoryControl != null) { var historyControlRect = new Rect(0f, 0f, Position.width, Position.height - rect.height); - var requiresRepaint = historyControl.Render(historyControlRect, - entry => { - selectedEntry = entry; - BuildTree(); + var requiresRepaint = HistoryControl.Render(historyControlRect, + singleClick: entry => { + SelectedEntry = entry; + BuildTreeChanges(); }, - entry => { }, entry => { - GenericMenu menu = new GenericMenu(); - menu.AddItem(new GUIContent("Revert"), false, RevertCommit); - menu.ShowAsContext(); - }); + doubleClick: entry => { + + }, + rightClick: historyControlRightClick); if (requiresRepaint) Redraw(); @@ -393,27 +395,28 @@ public override void OnGUI() DoProgressGUI(); - if (!selectedEntry.Equals(GitLogEntry.Default)) + 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); + HistoryControl.ScrollTo(HistoryControl.SelectedIndex); } + if (GUILayout.Button(ClearSelectionButton, Styles.ToolbarButtonStyle, GUILayout.ExpandWidth(false))) { - selectedEntry = GitLogEntry.Default; - historyControl.SelectedIndex = -1; + 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)); + DetailsScroll = GUILayout.BeginScrollView(DetailsScroll, GUILayout.Height(250)); { - HistoryDetailsEntry(selectedEntry); + HistoryDetailsEntry(SelectedEntry); GUILayout.Space(EditorGUIUtility.standardVerticalSpacing); GUILayout.Label("Files changed", EditorStyles.boldLabel); @@ -424,24 +427,23 @@ public override void OnGUI() 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 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) + 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) + 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, + singleClick: node => { }, + doubleClick: node => { }, + rightClick: changesTreeRightClick); + + if (TreeChanges.RequiresRepaint) Redraw(); } @@ -456,43 +458,74 @@ public override void OnGUI() } } - private void HistoryDetailsEntry(GitLogEntry entry) + public override void OnEnable() { - GUILayout.BeginVertical(Styles.HeaderBoxStyle); - GUILayout.Label(entry.Summary, Styles.HistoryDetailsTitleStyle); + base.OnEnable(); - GUILayout.Space(-5); + if (TreeChanges != null) + { + TreeChanges.ViewHasFocus = HasFocus; + TreeChanges.UpdateIcons(Styles.FolderIcon); + } - GUILayout.BeginHorizontal(); - GUILayout.Label(entry.PrettyTimeString, Styles.HistoryDetailsMetaInfoStyle); - GUILayout.Label(entry.AuthorName, Styles.HistoryDetailsMetaInfoStyle); - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); + AttachHandlers(Repository); + ValidateCachedData(Repository); + } - GUILayout.Space(3); - GUILayout.EndVertical(); + public override void OnDisable() + { + base.OnDisable(); + DetachHandlers(Repository); } - private void RevertCommit() + public override void OnDataUpdate() { - var dialogTitle = "Revert commit"; - var dialogBody = string.Format(@"Are you sure you want to revert the following commit:""{0}""", selectedEntry.Summary); + base.OnDataUpdate(); + MaybeUpdateData(); + } - if (EditorUtility.DisplayDialog(dialogTitle, dialogBody, "Revert", "Cancel")) + public override void OnFocusChanged() + { + base.OnFocusChanged(); + var hasFocus = HasFocus; + if (TreeChanges.ViewHasFocus != hasFocus) { - Repository - .Revert(selectedEntry.CommitID) - .FinallyInUI((success, e) => { - if (!success) - { - EditorUtility.DisplayDialog(dialogTitle, - "Error reverting commit: " + e.Message, Localization.Cancel); - } - }) - .Start(); + TreeChanges.ViewHasFocus = hasFocus; + Redraw(); } } + protected abstract void AttachHandlers(IRepository repository); + protected abstract void DetachHandlers(IRepository repository); + protected abstract void ValidateCachedData(IRepository repository); + protected abstract void MaybeUpdateData(); + } + + [Serializable] + class HistoryView : HistoryBase + { + [SerializeField] private bool currentLogHasUpdate; + [SerializeField] private bool currentTrackingStatusHasUpdate; + + [SerializeField] private List logEntries = new List(); + + [SerializeField] private int statusAhead; + + [SerializeField] private CacheUpdateEvent lastLogChangedEvent; + [SerializeField] private CacheUpdateEvent lastTrackingStatusChangedEvent; + + [SerializeField] private HistoryControl historyControl; + [SerializeField] private GitLogEntry selectedEntry = GitLogEntry.Default; + [SerializeField] private ChangesTree treeChanges = new ChangesTree { DisplayRootNode = false }; + [SerializeField] private Vector2 detailsScroll; + + public override void Refresh() + { + base.Refresh(); + Refresh(CacheType.GitLog); + Refresh(CacheType.GitAheadBehind); + } + private void RepositoryOnTrackingStatusChanged(CacheUpdateEvent cacheUpdateEvent) { if (!lastTrackingStatusChangedEvent.Equals(cacheUpdateEvent)) @@ -515,7 +548,7 @@ private void RepositoryOnLogChanged(CacheUpdateEvent cacheUpdateEvent) } } - private void AttachHandlers(IRepository repository) + protected override void AttachHandlers(IRepository repository) { if (repository == null) { @@ -526,7 +559,7 @@ private void AttachHandlers(IRepository repository) repository.LogChanged += RepositoryOnLogChanged; } - private void DetachHandlers(IRepository repository) + protected override void DetachHandlers(IRepository repository) { if (repository == null) { @@ -537,13 +570,13 @@ private void DetachHandlers(IRepository repository) repository.LogChanged -= RepositoryOnLogChanged; } - private void ValidateCachedData(IRepository repository) + protected override void ValidateCachedData(IRepository repository) { repository.CheckAndRaiseEventsIfCacheNewer(CacheType.GitLog, lastLogChangedEvent); repository.CheckAndRaiseEventsIfCacheNewer(CacheType.GitAheadBehind, lastTrackingStatusChangedEvent); } - private void MaybeUpdateData() + protected override void MaybeUpdateData() { if (Repository == null) { @@ -563,30 +596,205 @@ private void MaybeUpdateData() logEntries = Repository.CurrentLog; - BuildHistoryControl(); + BuildHistoryControl(statusAhead, logEntries); + } + } + + public override void OnGUI() + { + var lastRect = GUILayoutUtility.GetLastRect(); + DoHistoryGui(lastRect, entry => { + GenericMenu menu = new GenericMenu(); + menu.AddItem(new GUIContent("Revert"), false, RevertCommit); + menu.ShowAsContext(); + }, node => { + var menu = CreateChangesTreeContextMenu(node); + menu.ShowAsContext(); + }); + } + + protected override HistoryControl HistoryControl + { + get { return historyControl; } + set { historyControl = value; } + } + + protected override GitLogEntry SelectedEntry + { + get { return selectedEntry; } + set { selectedEntry = value; } + } + + protected override ChangesTree TreeChanges + { + get { return treeChanges; } + set { treeChanges = value; } + } + + protected override Vector2 DetailsScroll + { + get { return detailsScroll; } + set { detailsScroll = value; } + } + + private GenericMenu CreateChangesTreeContextMenu(ChangesTreeNode node) + { + var genericMenu = new GenericMenu(); + + genericMenu.AddItem(new GUIContent("Show History"), false, () => { }); + + return genericMenu; + } + } + + [Serializable] + class FileHistoryView : HistoryBase + { + [SerializeField] private bool currentFileLogHasUpdate; + [SerializeField] private bool currentStatusEntriesHasUpdate; + + [SerializeField] private GitFileLog gitFileLog; + + [SerializeField] private HistoryControl historyControl; + [SerializeField] private GitLogEntry selectedEntry = GitLogEntry.Default; + [SerializeField] private ChangesTree treeChanges = new ChangesTree { DisplayRootNode = false }; + [SerializeField] private Vector2 detailsScroll; + [SerializeField] private List gitStatusEntries = new List(); + + [SerializeField] private CacheUpdateEvent lastStatusEntriesChangedEvent; + [SerializeField] private CacheUpdateEvent lastFileLogChangedEvent; + + public override void Refresh() + { + base.Refresh(); + Refresh(CacheType.GitLog); + Refresh(CacheType.GitAheadBehind); + } + + private void RepositoryOnFileLogChanged(CacheUpdateEvent cacheUpdateEvent) + { + if (!lastFileLogChangedEvent.Equals(cacheUpdateEvent)) + { + ReceivedEvent(cacheUpdateEvent.cacheType); + lastFileLogChangedEvent = cacheUpdateEvent; + currentFileLogHasUpdate = true; + Redraw(); } } - private void BuildHistoryControl() + private void RepositoryOnStatusEntriesChanged(CacheUpdateEvent cacheUpdateEvent) { - if (historyControl == null) + if (!lastStatusEntriesChangedEvent.Equals(cacheUpdateEvent)) { - historyControl = new HistoryControl(); + ReceivedEvent(cacheUpdateEvent.cacheType); + lastStatusEntriesChangedEvent = cacheUpdateEvent; + currentStatusEntriesHasUpdate = true; + Redraw(); } + } - historyControl.Load(statusAhead, logEntries); - if (!selectedEntry.Equals(GitLogEntry.Default) - && selectedEntry.CommitID != historyControl.SelectedGitLogEntry.CommitID) + protected override void AttachHandlers(IRepository repository) + { + if (repository == null) { - selectedEntry = GitLogEntry.Default; + return; } + + repository.FileLogChanged += RepositoryOnFileLogChanged; + repository.StatusEntriesChanged += RepositoryOnStatusEntriesChanged; } - private void BuildTree() + protected override void DetachHandlers(IRepository repository) { - treeChanges.PathSeparator = Environment.FileSystem.DirectorySeparatorChar.ToString(); - treeChanges.Load(selectedEntry.changes.Select(entry => new GitStatusEntryTreeData(entry))); - Redraw(); + if (repository == null) + { + return; + } + + repository.FileLogChanged -= RepositoryOnFileLogChanged; + repository.FileLogChanged -= RepositoryOnStatusEntriesChanged; + } + + protected override void ValidateCachedData(IRepository repository) + { + repository.CheckAndRaiseEventsIfCacheNewer(CacheType.GitFileLog, lastFileLogChangedEvent); + } + + protected override void MaybeUpdateData() + { + if (Repository == null) + { + return; + } + + if (currentFileLogHasUpdate) + { + currentFileLogHasUpdate = false; + + gitFileLog = Repository.CurrentFileLog; + + BuildHistoryControl(0, gitFileLog.LogEntries); + } + + if (currentStatusEntriesHasUpdate) + { + currentStatusEntriesHasUpdate = false; + + gitStatusEntries = Repository.CurrentChanges; + } + } + + public override void OnGUI() + { + var lastRect = GUILayoutUtility.GetLastRect(); + DoHistoryGui(lastRect, entry => { + GenericMenu menu = new GenericMenu(); + string checkoutPrompt = string.Format("Checkout revision {0}", entry.ShortID); + menu.AddItem(new GUIContent(checkoutPrompt), false, () => Checkout(entry.commitID)); + menu.ShowAsContext(); + }, node => { + }); + } + + protected override HistoryControl HistoryControl + { + get { return historyControl; } + set { historyControl = value; } + } + + protected override GitLogEntry SelectedEntry + { + get { return selectedEntry; } + set { selectedEntry = value; } + } + + protected override ChangesTree TreeChanges + { + get { return treeChanges; } + set { treeChanges = value; } + } + + protected override Vector2 DetailsScroll + { + get { return detailsScroll; } + set { detailsScroll = value; } + } + + private const string ConfirmCheckoutTitle = "Discard Changes?"; + private const string ConfirmCheckoutMessage = "You've made changes to file '{0}'. Overwrite these changes with the historical version?"; + private const string ConfirmCheckoutOK = "Overwrite"; + private const string ConfirmCheckoutCancel = "Cancel"; + + protected void Checkout(string commitId) + { + var promptUser = gitStatusEntries.Count > 0 && gitStatusEntries.Any(statusEntry => gitFileLog.Path.Equals(statusEntry.Path.ToNPath())); + + if (!promptUser || EditorUtility.DisplayDialog(ConfirmCheckoutTitle, string.Format(ConfirmCheckoutMessage, gitFileLog.Path), ConfirmCheckoutOK, ConfirmCheckoutCancel)) + { + Repository.CheckoutVersion(commitId, new string[] { gitFileLog.Path }) + .ThenInUI(AssetDatabase.Refresh) + .Start(); + } } } } diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PopupWindow.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PopupWindow.cs index abeff1ab4..406a5e178 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PopupWindow.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PopupWindow.cs @@ -7,7 +7,7 @@ namespace GitHub.Unity { [Serializable] - class PopupWindow : BaseWindow + public class PopupWindow : BaseWindow { public enum PopupViewType {