diff --git a/src/GitHub.Api/Events/RepositoryWatcher.cs b/src/GitHub.Api/Events/RepositoryWatcher.cs index 066788dbc..b72d6bd3c 100644 --- a/src/GitHub.Api/Events/RepositoryWatcher.cs +++ b/src/GitHub.Api/Events/RepositoryWatcher.cs @@ -11,7 +11,7 @@ interface IRepositoryWatcher : IDisposable { void Start(); void Stop(); - event Action HeadChanged; + event Action HeadChanged; event Action IndexChanged; event Action ConfigChanged; event Action LocalBranchChanged; @@ -38,7 +38,7 @@ class RepositoryWatcher : IRepositoryWatcher private bool processingEvents; private readonly ManualResetEventSlim signalProcessingEventsDone = new ManualResetEventSlim(false); - public event Action HeadChanged; + public event Action HeadChanged; public event Action IndexChanged; public event Action ConfigChanged; public event Action LocalBranchChanged; @@ -162,14 +162,7 @@ public int CheckAndProcessEvents() private int ProcessEvents(Event[] fileEvents) { - var eventsProcessed = 0; - var configChanged = false; - var headChanged = false; - var repositoryChanged = false; - var indexChanged = false; - - string headContent = null; - + Dictionary> events = new Dictionary>(); foreach (var fileEvent in fileEvents) { if (!running) @@ -197,22 +190,17 @@ private int ProcessEvents(Event[] fileEvents) // handling events in .git/* if (fileA.IsChildOf(paths.DotGitPath)) { - if (!configChanged && fileA.Equals(paths.DotGitConfig)) + if (!events.ContainsKey(EventType.ConfigChanged) && fileA.Equals(paths.DotGitConfig)) { - configChanged = true; + events.Add(EventType.ConfigChanged, null); } - else if (!headChanged && fileA.Equals(paths.DotGitHead)) + else if (!events.ContainsKey(EventType.HeadChanged) && fileA.Equals(paths.DotGitHead)) { - if (fileEvent.Type != EventType.DELETED) - { - headContent = paths.DotGitHead.ReadAllLines().FirstOrDefault(); - } - - headChanged = true; + events.Add(EventType.HeadChanged, null); } - else if (!indexChanged && fileA.Equals(paths.DotGitIndex)) + else if (!events.ContainsKey(EventType.IndexChanged) && fileA.Equals(paths.DotGitIndex)) { - indexChanged = true; + events.Add(EventType.IndexChanged, null); } else if (fileA.IsChildOf(paths.RemotesPath)) { @@ -226,7 +214,7 @@ private int ProcessEvents(Event[] fileEvents) var origin = relativePathElements[0]; - if (fileEvent.Type == EventType.DELETED) + if (fileEvent.Type == sfw.net.EventType.DELETED) { if (fileA.ExtensionWithDot == ".lock") { @@ -234,12 +222,9 @@ private int ProcessEvents(Event[] fileEvents) } var branch = string.Join(@"/", relativePathElements.Skip(1).ToArray()); - - Logger.Trace("RemoteBranchDeleted: {0}/{1}", origin, branch); - RemoteBranchDeleted?.Invoke(origin, branch); - eventsProcessed++; + AddOrUpdateEventData(events, EventType.RemoteBranchDeleted, new EventData { Origin = origin, Branch = branch }); } - else if (fileEvent.Type == EventType.RENAMED) + else if (fileEvent.Type == sfw.net.EventType.RENAMED) { if (fileA.ExtensionWithDot != ".lock") { @@ -255,17 +240,14 @@ private int ProcessEvents(Event[] fileEvents) .Union(new[] { fileA.FileNameWithoutExtension }).ToArray(); var branch = string.Join(@"/", branchPathElement); - - Logger.Trace("RemoteBranchCreated: {0}/{1}", origin, branch); - RemoteBranchCreated?.Invoke(origin, branch); - eventsProcessed++; + AddOrUpdateEventData(events, EventType.RemoteBranchCreated, new EventData { Origin = origin, Branch = branch }); } } } } else if (fileA.IsChildOf(paths.BranchesPath)) { - if (fileEvent.Type == EventType.MODIFIED) + if (fileEvent.Type == sfw.net.EventType.MODIFIED) { if (fileA.DirectoryExists()) { @@ -287,11 +269,10 @@ private int ProcessEvents(Event[] fileEvents) var branch = string.Join(@"/", relativePathElements.ToArray()); - Logger.Trace("LocalBranchChanged: {0}", branch); - LocalBranchChanged?.Invoke(branch); - eventsProcessed++; + AddOrUpdateEventData(events, EventType.LocalBranchChanged, new EventData { Branch = branch }); + } - else if (fileEvent.Type == EventType.DELETED) + else if (fileEvent.Type == sfw.net.EventType.DELETED) { if (fileA.ExtensionWithDot == ".lock") { @@ -307,12 +288,9 @@ private int ProcessEvents(Event[] fileEvents) } var branch = string.Join(@"/", relativePathElements.ToArray()); - - Logger.Trace("LocalBranchDeleted: {0}", branch); - LocalBranchDeleted?.Invoke(branch); - eventsProcessed++; + AddOrUpdateEventData(events, EventType.LocalBranchDeleted, new EventData { Branch = branch }); } - else if (fileEvent.Type == EventType.RENAMED) + else if (fileEvent.Type == sfw.net.EventType.RENAMED) { if (fileA.ExtensionWithDot != ".lock") { @@ -332,10 +310,7 @@ private int ProcessEvents(Event[] fileEvents) } var branch = string.Join(@"/", relativePathElements.ToArray()); - - Logger.Trace("LocalBranchCreated: {0}", branch); - LocalBranchCreated?.Invoke(branch); - eventsProcessed++; + AddOrUpdateEventData(events, EventType.LocalBranchCreated, new EventData { Branch = branch }); } } } @@ -343,43 +318,109 @@ private int ProcessEvents(Event[] fileEvents) } else { - if (repositoryChanged || ignoredPaths.Any(ignoredPath => fileA.IsChildOf(ignoredPath))) + if (events.ContainsKey(EventType.RepositoryChanged) || ignoredPaths.Any(ignoredPath => fileA.IsChildOf(ignoredPath))) { continue; } - - repositoryChanged = true; + events.Add(EventType.RepositoryChanged, null); } } - if (configChanged) + return FireEvents(events); + } + + private void AddOrUpdateEventData(Dictionary> events, EventType type, EventData data) + { + if (!events.ContainsKey(type)) + events.Add(type, new List()); + events[type].Add(data); + } + + private int FireEvents(Dictionary> events) + { + int eventsProcessed = 0; + if (events.ContainsKey(EventType.ConfigChanged)) { Logger.Trace("ConfigChanged"); ConfigChanged?.Invoke(); eventsProcessed++; } - if (headChanged) + if (events.ContainsKey(EventType.HeadChanged)) { - Logger.Trace("HeadChanged: {0}", headContent ?? "[null]"); - HeadChanged?.Invoke(headContent); + Logger.Trace("HeadChanged"); + HeadChanged?.Invoke(); eventsProcessed++; } - if (indexChanged) + if (events.ContainsKey(EventType.IndexChanged)) { Logger.Trace("IndexChanged"); IndexChanged?.Invoke(); eventsProcessed++; } - if (repositoryChanged) + if (events.ContainsKey(EventType.RepositoryChanged)) { Logger.Trace("RepositoryChanged"); RepositoryChanged?.Invoke(); eventsProcessed++; } + List localBranchesCreated; + if (events.TryGetValue(EventType.LocalBranchCreated, out localBranchesCreated)) + { + foreach (var evt in localBranchesCreated) + { + Logger.Trace($"LocalBranchCreated: {evt.Branch}"); + LocalBranchCreated?.Invoke(evt.Branch); + eventsProcessed++; + } + } + + List localBranchesChanged; + if (events.TryGetValue(EventType.LocalBranchChanged, out localBranchesChanged)) + { + foreach (var evt in localBranchesChanged) + { + Logger.Trace($"LocalBranchChanged: {evt.Branch}"); + LocalBranchChanged?.Invoke(evt.Branch); + eventsProcessed++; + } + } + + List localBranchesDeleted; + if (events.TryGetValue(EventType.LocalBranchDeleted, out localBranchesDeleted)) + { + foreach (var evt in localBranchesDeleted) + { + Logger.Trace($"LocalBranchDeleted: {evt.Branch}"); + LocalBranchDeleted?.Invoke(evt.Branch); + eventsProcessed++; + } + } + + List remoteBranchesCreated; + if (events.TryGetValue(EventType.RemoteBranchCreated, out remoteBranchesCreated)) + { + foreach (var evt in remoteBranchesCreated) + { + Logger.Trace($"RemoteBranchCreated: {evt.Origin}/{evt.Branch}"); + RemoteBranchCreated?.Invoke(evt.Origin, evt.Branch); + eventsProcessed++; + } + } + + List remoteBranchesDeleted; + if (events.TryGetValue(EventType.RemoteBranchDeleted, out remoteBranchesDeleted)) + { + foreach (var evt in remoteBranchesDeleted) + { + Logger.Trace($"RemoteBranchDeleted: {evt.Origin}/{evt.Branch}"); + RemoteBranchDeleted?.Invoke(evt.Origin, evt.Branch); + eventsProcessed++; + } + } return eventsProcessed; } @@ -407,5 +448,25 @@ public void Dispose() } protected static ILogging Logger { get; } = Logging.GetLogger(); + + private enum EventType + { + None, + ConfigChanged, + HeadChanged, + RepositoryChanged, + IndexChanged, + RemoteBranchDeleted, + RemoteBranchCreated, + LocalBranchDeleted, + LocalBranchCreated, + LocalBranchChanged + } + + private class EventData + { + public string Origin; + public string Branch; + } } } diff --git a/src/GitHub.Api/Git/GitRemote.cs b/src/GitHub.Api/Git/GitRemote.cs index 0a7b1b26a..d5478d897 100644 --- a/src/GitHub.Api/Git/GitRemote.cs +++ b/src/GitHub.Api/Git/GitRemote.cs @@ -3,7 +3,7 @@ namespace GitHub.Unity { - enum GitRemoteFunction + public enum GitRemoteFunction { Unknown, Fetch, @@ -12,7 +12,7 @@ enum GitRemoteFunction } [Serializable] - struct GitRemote + public struct GitRemote { public string Name; public string Url; diff --git a/src/GitHub.Api/Git/IRepository.cs b/src/GitHub.Api/Git/IRepository.cs index 14cef8041..5818a9f52 100644 --- a/src/GitHub.Api/Git/IRepository.cs +++ b/src/GitHub.Api/Git/IRepository.cs @@ -45,24 +45,26 @@ public interface IRepository : IEquatable /// /// Gets the current remote of the repository. /// - ConfigRemote? CurrentRemote { get; set; } + GitRemote? CurrentRemote { get; } /// /// Gets the current branch of the repository. /// - ConfigBranch? CurrentBranch { get; set; } - GitStatus CurrentStatus { get; set; } + GitBranch? CurrentBranch { get; } + GitStatus CurrentStatus { get; } + IList Remotes { get; } IEnumerable LocalBranches { get; } IEnumerable RemoteBranches { get; } IUser User { get; set; } - IEnumerable CurrentLocks { get; } + IList CurrentLocks { get; } string CurrentBranchName { get; } - event Action OnStatusUpdated; - event Action OnActiveBranchChanged; - event Action OnActiveRemoteChanged; + event Action OnStatusChanged; + event Action OnCurrentBranchChanged; + event Action OnCurrentRemoteChanged; event Action OnLocalBranchListChanged; - event Action OnHeadChanged; - event Action> OnLocksUpdated; + event Action OnCurrentBranchUpdated; + event Action> OnLocksChanged; event Action OnRepositoryInfoChanged; + event Action OnRemoteBranchListChanged; } } \ No newline at end of file diff --git a/src/GitHub.Api/Git/Repository.cs b/src/GitHub.Api/Git/Repository.cs index 6da63347b..68d53af17 100644 --- a/src/GitHub.Api/Git/Repository.cs +++ b/src/GitHub.Api/Git/Repository.cs @@ -8,33 +8,30 @@ namespace GitHub.Unity { [DebuggerDisplay("{DebuggerDisplay,nq}")] - class Repository : IRepository, IEquatable + class Repository : IEquatable, IRepository { - private IRepositoryManager repositoryManager; private ConfigBranch? currentBranch; + private IList currentLocks; private ConfigRemote? currentRemote; private GitStatus currentStatus; - - public event Action OnStatusUpdated; - public event Action OnActiveBranchChanged; - public event Action OnActiveRemoteChanged; + private Dictionary localBranches = new Dictionary(); + private Dictionary> remoteBranches = new Dictionary>(); + private Dictionary remotes; + private IRepositoryManager repositoryManager; + public event Action OnCurrentBranchChanged; + public event Action OnCurrentRemoteChanged; + public event Action OnCurrentBranchUpdated; public event Action OnLocalBranchListChanged; - public event Action OnHeadChanged; - public event Action> OnLocksUpdated; + public event Action> OnLocksChanged; + public event Action OnRemoteBranchListChanged; public event Action OnRepositoryInfoChanged; - public IEnumerable LocalBranches => repositoryManager.LocalBranches.Values.Select( - x => new GitBranch(x.Name, (x.IsTracking ? (x.Remote.Value.Name + "/" + x.Name) : "[None]"), x.Name == CurrentBranch?.Name)); - - public IEnumerable RemoteBranches => repositoryManager.RemoteBranches.Values.SelectMany( - x => x.Values).Select(x => new GitBranch(x.Remote.Value.Name + "/" + x.Name, "[None]", false)); + public event Action OnStatusChanged; /// /// Initializes a new instance of the class. /// - /// /// The repository name. - /// The repository's clone URL. /// public Repository(string name, NPath localPath) { @@ -51,12 +48,18 @@ public void Initialize(IRepositoryManager repositoryManager) Guard.ArgumentNotNull(repositoryManager, nameof(repositoryManager)); this.repositoryManager = repositoryManager; - repositoryManager.OnLocalBranchListChanged += RepositoryManager_OnLocalBranchListChanged; - repositoryManager.OnCommitChanged += RepositoryManager_OnHeadChanged; - repositoryManager.OnLocksUpdated += RepositoryManager_OnLocksUpdated; - repositoryManager.OnStatusUpdated += RepositoryManager_OnStatusUpdated; - repositoryManager.OnActiveBranchChanged += RepositoryManager_OnActiveBranchChanged; - repositoryManager.OnActiveRemoteChanged += RepositoryManager_OnActiveRemoteChanged; + + repositoryManager.OnCurrentBranchUpdated += RepositoryManager_OnCurrentBranchUpdated; + repositoryManager.OnCurrentRemoteUpdated += RepositoryManager_OnCurrentRemoteUpdated; + repositoryManager.OnStatusUpdated += status => CurrentStatus = status; + repositoryManager.OnLocksUpdated += locks => CurrentLocks = locks; + repositoryManager.OnLocalBranchListUpdated += RepositoryManager_OnLocalBranchListUpdated; + repositoryManager.OnRemoteBranchListUpdated += RepositoryManager_OnRemoteBranchListUpdated; + repositoryManager.OnLocalBranchUpdated += RepositoryManager_OnLocalBranchUpdated; + repositoryManager.OnLocalBranchAdded += RepositoryManager_OnLocalBranchAdded; + repositoryManager.OnLocalBranchRemoved += RepositoryManager_OnLocalBranchRemoved; + repositoryManager.OnRemoteBranchAdded += RepositoryManager_OnRemoteBranchAdded; + repositoryManager.OnRemoteBranchRemoved += RepositoryManager_OnRemoteBranchRemoved; repositoryManager.OnGitUserLoaded += user => User = user; } @@ -111,7 +114,7 @@ public ITask Fetch() { return repositoryManager.Fetch(CurrentRemote.Value.Name); } - + public ITask Revert(string changeset) { return repositoryManager.Revert(changeset); @@ -134,112 +137,243 @@ public ITask ReleaseLock(string file, bool force) return repositoryManager.UnlockFile(file, force); } - private void SetCloneUrl() + /// + /// Note: We don't consider CloneUrl a part of the hash code because it can change during the lifetime + /// of a repository. Equals takes care of any hash collisions because of this + /// + /// + public override int GetHashCode() { - if (CurrentRemote.HasValue) - CloneUrl = new UriString(CurrentRemote.Value.Url); - else - CloneUrl = null; - Name = CloneUrl != null ? CloneUrl.RepositoryName : LocalPath.FileName; - OnRepositoryInfoChanged?.Invoke(); + return LocalPath.GetHashCode(); } - private void RepositoryManager_OnStatusUpdated(GitStatus status) + public override bool Equals(object obj) { - CurrentStatus = status; + if (ReferenceEquals(this, obj)) + return true; + var other = obj as Repository; + return Equals(other); } - private void RepositoryManager_OnActiveRemoteChanged(ConfigRemote? remote) + public bool Equals(Repository other) { - CurrentRemote = remote; + return Equals((IRepository)other); } - private void RepositoryManager_OnActiveBranchChanged(ConfigBranch? branch) + public bool Equals(IRepository other) { - CurrentBranch = branch; + if (ReferenceEquals(this, other)) + return true; + return other != null && + object.Equals(LocalPath, other.LocalPath); } - private void RepositoryManager_OnHeadChanged() + private void RepositoryManager_OnCurrentRemoteUpdated(ConfigRemote? remote) { - OnHeadChanged?.Invoke(); + if (!Nullable.Equals(currentRemote, remote)) + { + currentRemote = remote; + + Logger.Trace("OnCurrentRemoteChanged: {0}", currentRemote.HasValue ? currentRemote.Value.ToString() : "[NULL]"); + OnCurrentRemoteChanged?.Invoke(currentRemote.HasValue ? currentRemote.Value.Name : null); + + UpdateRepositoryInfo(); + } } - private void RepositoryManager_OnLocalBranchListChanged() + private void RepositoryManager_OnCurrentBranchUpdated(ConfigBranch? branch) { + if (!Nullable.Equals(currentBranch, branch)) + { + currentBranch = branch; + + Logger.Trace("OnCurrentBranchChanged: {0}", currentBranch.HasValue ? currentBranch.ToString() : "[NULL]"); + OnCurrentBranchChanged?.Invoke(currentBranch.HasValue ? currentBranch.Value.Name : null); + } + } + + private void RepositoryManager_OnLocalBranchUpdated(string name) + { + if (name == currentBranch?.Name) + { + Logger.Trace("OnCurrentBranchUpdated: {0}", name); + OnCurrentBranchUpdated?.Invoke(); + Refresh(); + } + } + + private void RepositoryManager_OnRemoteBranchListUpdated(Dictionary updatedRemotes, Dictionary> branches) + { + remotes = updatedRemotes; + + Remotes = remotes.Select(pair => GetGitRemote(pair.Value)).ToArray(); + + remoteBranches = branches; + + Logger.Trace("OnRemoteBranchListChanged"); + OnRemoteBranchListChanged?.Invoke(); + } + + private void RepositoryManager_OnLocalBranchListUpdated(Dictionary branches) + { + localBranches = branches; + + Logger.Trace("OnLocalBranchListChanged"); OnLocalBranchListChanged?.Invoke(); } - private void RepositoryManager_OnLocksUpdated(IEnumerable locks) + private void UpdateRepositoryInfo() { - CurrentLocks = locks; - OnLocksUpdated?.Invoke(CurrentLocks); + if (CurrentRemote.HasValue) + { + CloneUrl = new UriString(CurrentRemote.Value.Url); + Name = CloneUrl.RepositoryName; + Logger.Trace("CloneUrl: {0}", CloneUrl.ToString()); + } + else + { + CloneUrl = null; + Name = LocalPath.FileName; + Logger.Trace("CloneUrl: [NULL]"); + } + + OnRepositoryInfoChanged?.Invoke(); } - /// - /// Note: We don't consider CloneUrl a part of the hash code because it can change during the lifetime - /// of a repository. Equals takes care of any hash collisions because of this - /// - /// - public override int GetHashCode() + private void RepositoryManager_OnLocalBranchRemoved(string name) { - return LocalPath.GetHashCode(); + if (localBranches.ContainsKey(name)) + { + localBranches.Remove(name); + + Logger.Trace("OnLocalBranchListChanged"); + OnLocalBranchListChanged?.Invoke(); + } + else + { + Logger.Warning("Branch {0} is not found", name); + } } - public override bool Equals(object obj) + private void RepositoryManager_OnLocalBranchAdded(string name) { - if (ReferenceEquals(this, obj)) - return true; - var other = obj as Repository; - return Equals(other); + if (!localBranches.ContainsKey(name)) + { + var branch = repositoryManager.Config.GetBranch(name); + if (!branch.HasValue) + { + branch = new ConfigBranch { Name = name }; + } + localBranches.Add(name, branch.Value); + + Logger.Trace("OnLocalBranchListChanged"); + OnLocalBranchListChanged?.Invoke(); + } + else + { + Logger.Warning("Branch {0} is already present", name); + } } - public bool Equals(Repository other) + private void RepositoryManager_OnRemoteBranchAdded(string remote, string name) { - return (Equals((IRepository)other)); + Dictionary branchList; + if (remoteBranches.TryGetValue(remote, out branchList)) + { + if (!branchList.ContainsKey(name)) + { + branchList.Add(name, new ConfigBranch { Name = name, Remote = remotes[remote] }); + + Logger.Trace("OnRemoteBranchListChanged"); + OnRemoteBranchListChanged?.Invoke(); + } + else + { + Logger.Warning("Branch {0} is already present in Remote {1}", name, remote); + } + } + else + { + Logger.Warning("Remote {0} is not found", remote); + } } - public bool Equals(IRepository other) + private void RepositoryManager_OnRemoteBranchRemoved(string remote, string name) { - if (ReferenceEquals(this, other)) - return true; - return other != null && - object.Equals(LocalPath, other.LocalPath); + Dictionary branchList; + if (remoteBranches.TryGetValue(remote, out branchList)) + { + if (localBranches.ContainsKey(name)) + { + localBranches.Remove(name); + + Logger.Trace("OnRemoteBranchListChanged"); + OnRemoteBranchListChanged?.Invoke(); + } + else + { + Logger.Warning("Branch {0} is not found in Remote {1}", name, remote); + } + } + else + { + Logger.Warning("Remote {0} is not found", remote); + } + } + + private GitBranch GetLocalGitBranch(ConfigBranch x) + { + var name = x.Name; + var trackingName = x.IsTracking ? x.Remote.Value.Name + "/" + name : "[None]"; + var isActive = name == currentBranch?.Name; + + return new GitBranch(name, trackingName, isActive); + } + + private GitBranch GetRemoteGitBranch(ConfigBranch x) + { + var name = x.Remote.Value.Name + "/" + x.Name; + var trackingName = "[None]"; + + return new GitBranch(name, trackingName, false); } - public ConfigBranch? CurrentBranch + private GitRemote GetGitRemote(ConfigRemote configRemote) + { + return new GitRemote { Name = configRemote.Name, Url = configRemote.Url }; + } + + public IList Remotes { get; private set; } + + public IEnumerable LocalBranches => localBranches.Values.Select(GetLocalGitBranch); + + public IEnumerable RemoteBranches => remoteBranches.Values.SelectMany(x => x.Values).Select(GetRemoteGitBranch); + + public GitBranch? CurrentBranch { - get { return currentBranch; } - set + get { - if (currentBranch.HasValue != value.HasValue || (currentBranch.HasValue && !currentBranch.Value.Equals(value.Value))) + if (currentBranch != null) { - currentBranch = value; - Logger.Trace("OnActiveBranchChanged: {0}", value?.ToString() ?? "NULL"); - OnActiveBranchChanged?.Invoke(CurrentBranch.HasValue ? CurrentBranch.Value.Name : null); + return GetLocalGitBranch(currentBranch.Value); } + + return null; } } - /// - /// Gets the current branch of the repository. - /// public string CurrentBranchName => currentBranch?.Name; - /// - /// Gets the current remote of the repository. - /// - public ConfigRemote? CurrentRemote + public GitRemote? CurrentRemote { - get { return currentRemote; } - set + get { - if (currentRemote.HasValue != value.HasValue || (currentRemote.HasValue && !currentRemote.Value.Equals(value.Value))) + if (currentRemote != null) { - currentRemote = value; - SetCloneUrl(); - Logger.Trace("OnActiveRemoteChanged: {0}", value?.ToString() ?? "NULL"); - OnActiveRemoteChanged?.Invoke(CurrentRemote.HasValue ? CurrentRemote.Value.Name : null); + return GetGitRemote(currentRemote.Value); } + + return null; } } @@ -267,16 +401,27 @@ public ConfigRemote? CurrentRemote public GitStatus CurrentStatus { get { return currentStatus; } - set + private set { - Logger.Trace("OnStatusUpdated: {0}", value.ToString()); currentStatus = value; - OnStatusUpdated?.Invoke(CurrentStatus); + Logger.Trace("OnStatusChanged: {0}", value.ToString()); + OnStatusChanged?.Invoke(value); } } public IUser User { get; set; } - public IEnumerable CurrentLocks { get; private set; } + + public IList CurrentLocks + { + get { return currentLocks; } + private set + { + Logger.Trace("OnLocksChanged: {0}", value.ToString()); + currentLocks = value; + OnLocksChanged?.Invoke(value); + } + } + protected static ILogging Logger { get; } = Logging.GetLogger(); } @@ -289,12 +434,12 @@ public interface IUser [Serializable] class User : IUser { - public string Name { get; set; } - public string Email { get; set; } - public override string ToString() { return String.Format("Name: {0} Email: {1}", Name, Email); } + + public string Name { get; set; } + public string Email { get; set; } } } \ No newline at end of file diff --git a/src/GitHub.Api/Git/RepositoryManager.cs b/src/GitHub.Api/Git/RepositoryManager.cs index c8dc66aba..e3892806a 100644 --- a/src/GitHub.Api/Git/RepositoryManager.cs +++ b/src/GitHub.Api/Git/RepositoryManager.cs @@ -8,22 +8,20 @@ namespace GitHub.Unity { public interface IRepositoryManager : IDisposable { + event Action OnCurrentBranchUpdated; + event Action OnCurrentRemoteUpdated; + event Action OnGitUserLoaded; event Action OnIsBusyChanged; - - event Action OnCommitChanged; - event Action OnLocalBranchListChanged; - event Action OnRemoteBranchListChanged; + event Action OnLocalBranchAdded; + event Action> OnLocalBranchListUpdated; + event Action OnLocalBranchRemoved; + event Action OnLocalBranchUpdated; + event Action> OnLocksUpdated; + event Action OnRemoteBranchAdded; + event Action, Dictionary>> OnRemoteBranchListUpdated; + event Action OnRemoteBranchRemoved; event Action OnStatusUpdated; - event Action OnActiveBranchChanged; - event Action OnActiveRemoteChanged; - event Action OnGitUserLoaded; - event Action> OnLocksUpdated; - Dictionary LocalBranches { get; } - Dictionary> RemoteBranches { get; } - IGitConfig Config { get; } - IGitClient GitClient { get; } - bool IsBusy { get; } void Initialize(); void Start(); void Stop(); @@ -45,6 +43,10 @@ public interface IRepositoryManager : IDisposable ITask LockFile(string file); ITask UnlockFile(string file, bool force); int WaitForEvents(); + + IGitConfig Config { get; } + IGitClient GitClient { get; } + bool IsBusy { get; } } interface IRepositoryPathConfiguration @@ -92,7 +94,6 @@ public RepositoryPathConfiguration(NPath repositoryPath) class RepositoryManager : IRepositoryManager { - private readonly Dictionary branches = new Dictionary(); private readonly CancellationToken cancellationToken; private readonly IGitConfig config; private readonly IGitClient gitClient; @@ -102,41 +103,21 @@ class RepositoryManager : IRepositoryManager private readonly IUsageTracker usageTracker; private readonly IRepositoryWatcher watcher; - private string head; private bool isBusy; - private IEnumerable locks; - private Dictionary> remoteBranches = new Dictionary>(); - private Dictionary remotes; - private Action repositoryUpdateCallback; - private ConfigBranch? activeBranch; - // internal busy flag signal + public event Action OnCurrentBranchUpdated; + public event Action OnCurrentRemoteUpdated; + public event Action OnGitUserLoaded; public event Action OnIsBusyChanged; - - // branches loaded from config file - public event Action OnLocalBranchListChanged; - public event Action OnRemoteBranchListChanged; - - public event Action> OnLocksUpdated; - - public event Action OnCommitChanged; + public event Action OnLocalBranchAdded; + public event Action> OnLocalBranchListUpdated; + public event Action OnLocalBranchRemoved; + public event Action OnLocalBranchUpdated; + public event Action> OnLocksUpdated; + public event Action OnRemoteBranchAdded; + public event Action, Dictionary>> OnRemoteBranchListUpdated; + public event Action OnRemoteBranchRemoved; public event Action OnStatusUpdated; - public event Action OnActiveBranchChanged; - public event Action OnActiveRemoteChanged; - public event Action OnGitUserLoaded; - - public static RepositoryManager CreateInstance(IPlatform platform, ITaskManager taskManager, IUsageTracker usageTracker, - IGitClient gitClient, NPath repositoryRoot) - { - var repositoryPathConfiguration = new RepositoryPathConfiguration(repositoryRoot); - string filePath = repositoryPathConfiguration.DotGitConfig; - var gitConfig = new GitConfig(filePath); - - var repositoryWatcher = new RepositoryWatcher(platform, repositoryPathConfiguration, taskManager.Token); - - return new RepositoryManager(platform, taskManager, usageTracker, gitConfig, repositoryWatcher, - gitClient, repositoryPathConfiguration, taskManager.Token); - } public RepositoryManager(IPlatform platform, ITaskManager taskManager, IUsageTracker usageTracker, IGitConfig gitConfig, IRepositoryWatcher repositoryWatcher, IGitClient gitClient, @@ -154,6 +135,19 @@ public RepositoryManager(IPlatform platform, ITaskManager taskManager, IUsageTra SetupWatcher(); } + public static RepositoryManager CreateInstance(IPlatform platform, ITaskManager taskManager, IUsageTracker usageTracker, + IGitClient gitClient, NPath repositoryRoot) + { + var repositoryPathConfiguration = new RepositoryPathConfiguration(repositoryRoot); + string filePath = repositoryPathConfiguration.DotGitConfig; + var gitConfig = new GitConfig(filePath); + + var repositoryWatcher = new RepositoryWatcher(platform, repositoryPathConfiguration, taskManager.Token); + + return new RepositoryManager(platform, taskManager, usageTracker, gitConfig, repositoryWatcher, + gitClient, repositoryPathConfiguration, taskManager.Token); + } + public void Initialize() { Logger.Trace("Initialize"); @@ -164,8 +158,7 @@ public void Start() { Logger.Trace("Start"); - ReadHead(); - RefreshConfigData(); + UpdateConfigData(); LoadGitUser(); watcher.Start(); } @@ -249,7 +242,7 @@ public ITask RemoteAdd(string remote, string url) if (!platform.Environment.IsWindows) { task.Then(_ => { - RefreshConfigData(true); + UpdateConfigData(true); }); } return task; @@ -262,7 +255,7 @@ public ITask RemoteRemove(string remote) if (!platform.Environment.IsWindows) { task.Then(_ => { - RefreshConfigData(true); + UpdateConfigData(true); }); } return task; @@ -296,13 +289,12 @@ public ITask ListLocks(bool local) { var task = GitClient .ListLocks(local) - .Then((s, t) => + .Then((success, locks) => { - if (locks == null || !locks.SequenceEqual(t)) + if (success) { - locks = t; Logger.Trace("OnLocksUpdated"); - OnLocksUpdated(locks); + OnLocksUpdated?.Invoke(locks); } }); return HookupHandlers(task); @@ -331,7 +323,10 @@ private void LoadGitUser() .Then((success, value) => user.Name = value).Then( GitClient.GetConfig("user.email", GitConfigSource.User) .Then((success, value) => user.Email = value)) - .Then(() => OnGitUserLoaded?.Invoke(user)) + .Then(() => { + Logger.Trace("OnGitUserLoaded: {0}", user); + OnGitUserLoaded?.Invoke(user); + }) .Start(); } @@ -348,9 +343,11 @@ private void SetupWatcher() watcher.RemoteBranchDeleted += Watcher_OnRemoteBranchDeleted; } - private void ReadHead() + private void UpdateHead() { - head = repositoryPaths.DotGitHead.ReadAllLines().FirstOrDefault(); + var head = repositoryPaths.DotGitHead.ReadAllLines().FirstOrDefault(); + Logger.Trace("UpdateHead: {0}", head ?? "[NULL]"); + UpdateCurrentBranchAndRemote(head); } private ITask HookupHandlers(ITask task, bool disableWatcher = false) @@ -380,16 +377,17 @@ private ITask HookupHandlers(ITask task, bool disableWatcher = false) private void Watcher_OnRemoteBranchDeleted(string remote, string name) { - RemoveRemoteBranch(remote, name); + OnRemoteBranchRemoved?.Invoke(remote, name); } private void Watcher_OnRemoteBranchCreated(string remote, string name) { - AddRemoteBranch(remote, name); + OnRemoteBranchAdded?.Invoke(remote, name); } private void Watcher_OnRepositoryChanged() { + Logger.Trace("OnRepositoryChanged"); UpdateGitStatus(); } @@ -400,12 +398,12 @@ private void UpdateGitStatus() var task = GitClient.Status() .Finally((success, ex, data) => { - Logger.Trace($"GitStatus update: {success} {(data.HasValue ? data.Value.ToString() : "null")}"); + Logger.Trace($"GitStatus update: {success} {(data.HasValue ? data.Value.ToString() : "[null]")}"); if (success && data.HasValue) { OnStatusUpdated?.Invoke(data.Value); + Logger.Trace("Updated Git Status"); } - Logger.Trace("Updated Git Status"); }); HookupHandlers(task).Start(); @@ -413,67 +411,105 @@ private void UpdateGitStatus() private void Watcher_OnConfigChanged() { - RefreshConfigData(true); + UpdateConfigData(true); } - private void Watcher_OnHeadChanged(string contents) + private void Watcher_OnHeadChanged() { Logger.Trace("Watcher_OnHeadChanged"); - head = contents; - OnActiveBranchChanged?.Invoke(GetActiveBranch()); - OnActiveRemoteChanged?.Invoke(GetActiveRemote()); + UpdateHead(); UpdateGitStatus(); } + private void UpdateCurrentBranchAndRemote(string head) + { + ConfigBranch? branch = null; + + if (head.StartsWith("ref:")) + { + var branchName = head.Substring(head.IndexOf("refs/heads/") + "refs/heads/".Length); + branch = config.GetBranch(branchName); + + if (!branch.HasValue) + { + branch = new ConfigBranch { Name = branchName }; + } + } + + var defaultRemote = "origin"; + ConfigRemote? remote = null; + + if (branch.HasValue && branch.Value.IsTracking) + { + remote = branch.Value.Remote; + } + + if (!remote.HasValue) + { + remote = config.GetRemote(defaultRemote); + } + + if (!remote.HasValue) + { + var configRemotes = config.GetRemotes().ToArray(); + if (configRemotes.Any()) + { + remote = configRemotes.FirstOrDefault(); + } + } + + Logger.Trace("OnCurrentBranchUpdated: {0}", branch.HasValue ? branch.Value.ToString() : "[NULL]"); + OnCurrentBranchUpdated?.Invoke(branch); + + Logger.Trace("OnCurrentRemoteUpdated: {0}", remote.HasValue ? remote.Value.ToString() : "[NULL]"); + OnCurrentRemoteUpdated?.Invoke(remote); + } + private void Watcher_OnIndexChanged() {} private void Watcher_OnLocalBranchCreated(string name) { - AddLocalBranch(name); + OnLocalBranchAdded?.Invoke(name); } private void Watcher_OnLocalBranchDeleted(string name) { - RemoveLocalBranch(name); + OnLocalBranchRemoved?.Invoke(name); } private void Watcher_OnLocalBranchChanged(string name) { - if (name == activeBranch?.Name) - { - // commit of current branch changed, trigger OnHeadChanged - OnCommitChanged?.Invoke(); - UpdateGitStatus(); - } + OnLocalBranchUpdated?.Invoke(name); } - private void RefreshConfigData(bool resetConfig = false) + private void UpdateConfigData(bool resetConfig = false) { + Logger.Trace("UpdateConfigData reset:{0}", resetConfig); + if (resetConfig) { config.Reset(); } - Logger.Trace("RefreshConfigData"); - LoadBranchesFromConfig(); LoadRemotesFromConfig(); - - OnActiveBranchChanged?.Invoke(GetActiveBranch()); - OnActiveRemoteChanged?.Invoke(GetActiveRemote()); + UpdateHead(); } private void LoadBranchesFromConfig() { - branches.Clear(); - LoadBranchesFromConfig(repositoryPaths.BranchesPath, config.GetBranches().Where(x => x.IsTracking), ""); + Logger.Trace("LoadBranchesFromConfig"); + + var branches = new Dictionary(); + LoadBranchesFromConfig(branches, repositoryPaths.BranchesPath, config.GetBranches().Where(x => x.IsTracking), ""); + + Logger.Trace("OnLocalBranchListUpdated {0} branches", branches.Count); + OnLocalBranchListUpdated?.Invoke(branches); } - private void LoadBranchesFromConfig(NPath path, IEnumerable configBranches, string prefix) + private void LoadBranchesFromConfig(Dictionary branches, NPath path, IEnumerable configBranches, string prefix) { - Logger.Trace("LoadBranchesFromConfig"); - foreach (var file in path.Files()) { var branchName = prefix + file.FileName; @@ -488,7 +524,7 @@ private void LoadBranchesFromConfig(NPath path, IEnumerable config foreach (var dir in path.Directories()) { - LoadBranchesFromConfig(dir, configBranches, prefix + dir.FileName + "/"); + LoadBranchesFromConfig(branches, dir, configBranches, prefix + dir.FileName + "/"); } } @@ -496,8 +532,8 @@ private void LoadRemotesFromConfig() { Logger.Trace("LoadRemotesFromConfig"); - remotes = config.GetRemotes().ToDictionary(x => x.Name, x => x); - remoteBranches = new Dictionary>(); + var remotes = config.GetRemotes().ToArray().ToDictionary(x => x.Name, x => x); + var remoteBranches = new Dictionary>(); foreach (var remote in remotes.Keys) { @@ -516,103 +552,9 @@ private void LoadRemotesFromConfig() remoteBranches.Add(remote, branchList); } } - } - - private ConfigRemote? GetActiveRemote(string defaultRemote = "origin") - { - if (activeBranch.HasValue && activeBranch.Value.IsTracking) - { - return activeBranch.Value.Remote; - } - - var remote = config.GetRemote(defaultRemote); - if (remote.HasValue) - { - return remote; - } - - using (var remoteEnumerator = config.GetRemotes().GetEnumerator()) - { - if (remoteEnumerator.MoveNext()) - { - return remoteEnumerator.Current; - } - } - - return null; - } - - private ConfigBranch? GetActiveBranch() - { - if (head.StartsWith("ref:")) - { - var branch = head.Substring(head.IndexOf("refs/heads/") + "refs/heads/".Length); - activeBranch = GetBranch(branch); - } - else - { - activeBranch = null; - } - return activeBranch; - } - - private ConfigBranch? GetBranch(string name) - { - if (branches.ContainsKey(name)) - { - return branches[name]; - } - - return null; - } - - private void AddLocalBranch(string name) - { - if (!branches.ContainsKey(name)) - { - var branch = config.GetBranch(name); - if (!branch.HasValue) - { - branch = new ConfigBranch { Name = name }; - } - branches.Add(name, branch.Value); - OnLocalBranchListChanged?.Invoke(); - } - } - private void RemoveLocalBranch(string oldName) - { - if (branches.ContainsKey(oldName)) - { - branches.Remove(oldName); - OnLocalBranchListChanged?.Invoke(); - } - } - - private void AddRemoteBranch(string remote, string name) - { - Dictionary branchList = null; - if (remoteBranches.TryGetValue(remote, out branchList)) - { - if (!branchList.ContainsKey(name)) - { - branchList.Add(name, new ConfigBranch { Name = name, Remote = remotes[remote] }); - OnRemoteBranchListChanged?.Invoke(); - } - } - } - - private void RemoveRemoteBranch(string remote, string name) - { - Dictionary branchList = null; - if (remoteBranches.TryGetValue(remote, out branchList)) - { - if (branches.ContainsKey(name)) - { - branches.Remove(name); - OnRemoteBranchListChanged?.Invoke(); - } - } + Logger.Trace("OnRemoteBranchListUpdated {0} remotes", remotes.Count); + OnRemoteBranchListUpdated?.Invoke(remotes, remoteBranches); } private bool disposed; @@ -635,9 +577,6 @@ public void Dispose() GC.SuppressFinalize(this); } - public Dictionary LocalBranches => branches; - public Dictionary> RemoteBranches => remoteBranches; - public IGitConfig Config => config; public IGitClient GitClient => gitClient; diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index 3f77ce74f..8c00a6f63 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -40,7 +40,6 @@ class BranchesView : Subview [NonSerialized] private List favorites = new List(); [NonSerialized] private int listID = -1; - [NonSerialized] private List newLocalBranches; [NonSerialized] private BranchTreeNode newNodeSelection; [NonSerialized] private BranchesMode targetMode; [NonSerialized] private bool favoritesHasChanged; @@ -65,6 +64,7 @@ public override void OnEnable() base.OnEnable(); AttachHandlers(Repository); favoritesHasChanged = true; + Refresh(); } public override void OnDisable() @@ -100,45 +100,43 @@ private void AttachHandlers(IRepository repository) if (repository == null) return; - repository.OnLocalBranchListChanged += RunRefreshEmbeddedOnMainThread; - repository.OnActiveBranchChanged += HandleRepositoryBranchChangeEvent; - repository.OnActiveRemoteChanged += HandleRepositoryBranchChangeEvent; + repository.OnLocalBranchListChanged += RunUpdateBranchesOnMainThread; + repository.OnCurrentBranchChanged += HandleRepositoryBranchChangeEvent; + repository.OnCurrentRemoteChanged += HandleRepositoryBranchChangeEvent; } private void DetachHandlers(IRepository repository) { if (repository == null) return; - repository.OnLocalBranchListChanged -= RunRefreshEmbeddedOnMainThread; - repository.OnActiveBranchChanged -= HandleRepositoryBranchChangeEvent; - repository.OnActiveRemoteChanged -= HandleRepositoryBranchChangeEvent; - } - - private void RunRefreshEmbeddedOnMainThread() - { - new ActionTask(TaskManager.Token, _ => RefreshEmbedded()) - .ScheduleUI(TaskManager); + repository.OnLocalBranchListChanged -= RunUpdateBranchesOnMainThread; + repository.OnCurrentBranchChanged -= HandleRepositoryBranchChangeEvent; + repository.OnCurrentRemoteChanged -= HandleRepositoryBranchChangeEvent; } private void HandleRepositoryBranchChangeEvent(string obj) { - RunRefreshEmbeddedOnMainThread(); + RunUpdateBranchesOnMainThread(); } public override void Refresh() { base.Refresh(); + UpdateBranches(); + } - RefreshEmbedded(); + private void RunUpdateBranchesOnMainThread() + { + new ActionTask(TaskManager.Token, _ => UpdateBranches()) + .ScheduleUI(TaskManager); } - public void RefreshEmbedded() + public void UpdateBranches() { if (Repository == null) return; - OnLocalBranchesUpdate(Repository.LocalBranches); - OnRemoteBranchesUpdate(Repository.RemoteBranches); + BuildTree(Repository.LocalBranches, Repository.RemoteBranches); } public override void OnGUI() @@ -292,17 +290,6 @@ private bool IsFavorite(string branchName) return !String.IsNullOrEmpty(branchName) && favoritesList.Contains(branchName); } - private void OnLocalBranchesUpdate(IEnumerable list) - { - newLocalBranches = new List(list); - } - - private void OnRemoteBranchesUpdate(IEnumerable list) - { - BuildTree(newLocalBranches, list); - newLocalBranches.Clear(); - } - private void BuildTree(IEnumerable local, IEnumerable remote) { //Clear the selected node @@ -536,7 +523,7 @@ private void OnButtonBarGUI() .FinallyInUI((success, e) => { if (success) { - Refresh(); + Redraw(); } else { @@ -657,7 +644,7 @@ private void OnTreeNodeGUI(BranchTreeNode node) { if (success) { - Refresh(); + Redraw(); } else { @@ -693,7 +680,7 @@ private void OnTreeNodeGUI(BranchTreeNode node) { if (success) { - Refresh(); + Redraw(); } else { diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ChangesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ChangesView.cs index 9938072fb..2c560ef44 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ChangesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ChangesView.cs @@ -40,7 +40,7 @@ public override void OnEnable() return; OnStatusUpdate(Repository.CurrentStatus); - Repository.OnStatusUpdated += RunStatusUpdateOnMainThread; + Repository.OnStatusChanged += RunStatusUpdateOnMainThread; Repository.Refresh(); } @@ -49,7 +49,7 @@ public override void OnDisable() base.OnDisable(); if (Repository == null) return; - Repository.OnStatusUpdated -= RunStatusUpdateOnMainThread; + Repository.OnStatusChanged -= RunStatusUpdateOnMainThread; } private void RunStatusUpdateOnMainThread(GitStatus status) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs index f2cfcf278..f5b5f2fc9 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs @@ -43,7 +43,7 @@ class HistoryView : Subview [NonSerialized] private float scrollOffset; [NonSerialized] private DateTimeOffset scrollTime = DateTimeOffset.Now; [NonSerialized] private int selectionIndex; - [NonSerialized] private bool updated = true; + [NonSerialized] private bool logHasChanged; [NonSerialized] private bool useScrollTime; [NonSerialized] private bool isBusy; @@ -74,6 +74,7 @@ public override void OnEnable() { base.OnEnable(); AttachHandlers(Repository); + CheckLogCache(); } public override void OnDisable() @@ -94,13 +95,6 @@ public override void OnRepositoryChanged(IRepository oldRepository) DetachHandlers(oldRepository); AttachHandlers(Repository); - Refresh(); - } - - public override void Refresh() - { - base.Refresh(); - RefreshLog(); } public override void OnSelectionChange() @@ -108,7 +102,6 @@ public override void OnSelectionChange() if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(Selection.activeObject))) { historyTarget = Selection.activeObject; - Refresh(); } } @@ -117,24 +110,42 @@ public override void OnGUI() OnEmbeddedGUI(); } + public void CheckLogCache() + { + string firstItemCommitID = null; + if (history.Any()) + { + firstItemCommitID = history.First().CommitID; + } + + var cachedList = GitLogCache.Instance.Log; + + string firstCachedItemCommitID = null; + if (cachedList.Any()) + { + firstCachedItemCommitID = cachedList.First().CommitID; + } + + if (firstItemCommitID != firstCachedItemCommitID) + { + Logger.Trace("CommitID {0} != Cached CommitId {1}", firstItemCommitID ?? "[NULL]", firstCachedItemCommitID ?? "[NULL]"); + logHasChanged = true; + Redraw(); + } + } + private void AttachHandlers(IRepository repository) { if (repository == null) return; - repository.OnHeadChanged += Refresh; - repository.OnStatusUpdated += UpdateStatusOnMainThread; - repository.OnActiveBranchChanged += s => Refresh(); - repository.OnActiveRemoteChanged += s => Refresh(); + repository.OnStatusChanged += UpdateStatusOnMainThread; } private void DetachHandlers(IRepository repository) { if (repository == null) return; - repository.OnHeadChanged -= Refresh; - repository.OnStatusUpdated -= UpdateStatusOnMainThread; - repository.OnActiveBranchChanged -= s => Refresh(); - repository.OnActiveRemoteChanged -= s => Refresh(); + repository.OnStatusChanged -= UpdateStatusOnMainThread; } private void UpdateStatusOnMainThread(GitStatus status) @@ -149,68 +160,52 @@ private void UpdateStatus(GitStatus status) statusBehind = status.Behind; } - private void RefreshLog() - { - if (Repository != null) - { - Repository.Log().ThenInUI((success, log) => { - if (success) OnLogUpdate(log); - }).Start(); - } - } - - private void OnLogUpdate(List entries) - { - Logger.Trace("OnLogUpdate"); - GitLogCache.Instance.Log = entries; - updated = true; - Redraw(); - } - private void MaybeUpdateData() { isPublished = Repository != null && Repository.CurrentRemote.HasValue; currentRemote = isPublished ? Repository.CurrentRemote.Value.Name : "placeholder"; - if (!updated) - return; - updated = false; + if (logHasChanged) + { + logHasChanged = false; - history = GitLogCache.Instance.Log; + history = GitLogCache.Instance.Log; - if (history.Any()) - { - // Make sure that scroll as much as possible focuses the same time period in the new entry list - if (useScrollTime) + if (history.Any()) { - var closestIndex = -1; - double closestDifference = Mathf.Infinity; - for (var index = 0; index < history.Count; ++index) + // Make sure that scroll as much as possible focuses the same time period in the new entry list + if (useScrollTime) { - var diff = Math.Abs((history[index].Time - scrollTime).TotalSeconds); - if (diff < closestDifference) + var closestIndex = -1; + double closestDifference = Mathf.Infinity; + for (var index = 0; index < history.Count; ++index) { - closestDifference = diff; - closestIndex = index; + var diff = Math.Abs((history[index].Time - scrollTime).TotalSeconds); + if (diff < closestDifference) + { + closestDifference = diff; + closestIndex = index; + } } + + ScrollTo(closestIndex, scrollOffset); } - ScrollTo(closestIndex, scrollOffset); + CullHistory(); } - 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) + // Restore selection index or clear it + newSelectionIndex = -1; + if (!string.IsNullOrEmpty(selectionID)) { - selectionID = string.Empty; + selectionIndex = Enumerable.Range(1, history.Count + 1) + .FirstOrDefault( + index => history[index - 1].CommitID.Equals(selectionID)) - 1; + + if (selectionIndex < 0) + { + selectionID = string.Empty; + } } } } @@ -305,7 +300,7 @@ public void OnEmbeddedGUI() // Only update time scroll var lastScroll = scroll; scroll = GUILayout.BeginScrollView(scroll); - if (lastScroll != scroll && !updated) + if (lastScroll != scroll && !logHasChanged) { scrollTime = history[historyStartIndex].Time; scrollOffset = scroll.y - historyStartIndex * EntryHeight; @@ -411,7 +406,7 @@ public void OnEmbeddedGUI() if (Event.current.type == EventType.Repaint) { CullHistory(); - updated = false; + logHasChanged = false; if (newSelectionIndex >= 0 || newSelectionIndex == -2) { diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ProjectWindowInterface.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ProjectWindowInterface.cs index cef86203e..05fc0d66c 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ProjectWindowInterface.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ProjectWindowInterface.cs @@ -30,8 +30,8 @@ public static void Initialize(IRepository repo) repository = repo; if (repository != null) { - repository.OnStatusUpdated += RunStatusUpdateOnMainThread; - repository.OnLocksUpdated += RunLocksUpdateOnMainThread; + repository.OnStatusChanged += RunStatusUpdateOnMainThread; + repository.OnLocksChanged += RunLocksUpdateOnMainThread; } } diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/SettingsView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/SettingsView.cs index 781fecde9..6bec1cb99 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/SettingsView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/SettingsView.cs @@ -118,8 +118,8 @@ private void AttachHandlers(IRepository repository) if (repository == null) return; - repository.OnActiveRemoteChanged += Repository_OnActiveRemoteChanged; - repository.OnLocksUpdated += RunLocksUpdateOnMainThread; + repository.OnCurrentRemoteChanged += Repository_OnActiveRemoteChanged; + repository.OnLocksChanged += RunLocksUpdateOnMainThread; } private void DetachHandlers(IRepository repository) @@ -127,8 +127,8 @@ private void DetachHandlers(IRepository repository) if (repository == null) return; - repository.OnActiveRemoteChanged -= Repository_OnActiveRemoteChanged; - repository.OnLocksUpdated -= RunLocksUpdateOnMainThread; + repository.OnCurrentRemoteChanged -= Repository_OnActiveRemoteChanged; + repository.OnLocksChanged -= RunLocksUpdateOnMainThread; } public override void OnGUI() diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs index cf9da47f6..a9bbaca1b 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs @@ -144,6 +144,7 @@ public override void OnRepositoryChanged(IRepository oldRepository) if (ActiveView != null) ActiveView.OnRepositoryChanged(oldRepository); + UpdateLog(); } public override void OnSelectionChange() @@ -243,14 +244,17 @@ private void AttachHandlers(IRepository repository) if (repository == null) return; repository.OnRepositoryInfoChanged += RefreshOnMainThread; + repository.OnCurrentBranchUpdated += UpdateLog; } - + private void DetachHandlers(IRepository repository) { if (repository == null) return; repository.OnRepositoryInfoChanged -= RefreshOnMainThread; + repository.OnCurrentBranchUpdated -= UpdateLog; } + private void DoHeaderGUI() { GUILayout.BeginHorizontal(Styles.HeaderBoxStyle); @@ -393,6 +397,29 @@ private static SubTab TabButton(SubTab tab, string title, SubTab activeTab) return GUILayout.Toggle(activeTab == tab, title, EditorStyles.toolbarButton) ? tab : activeTab; } + private void UpdateLog() + { + if (Repository != null) + { + Logger.Trace("Updating Log"); + + Repository + .Log() + .FinallyInUI((success, exception, log) => { + if (success) + { + Logger.Trace("Updated Log"); + GitLogCache.Instance.Log = log; + + if (activeTab == SubTab.History) + { + HistoryView.CheckLogCache(); + } + } + }).Start(); + } + } + private Subview ToView(SubTab tab) { switch (tab) diff --git a/src/tests/IntegrationTests/BaseIntegrationTest.cs b/src/tests/IntegrationTests/BaseIntegrationTest.cs index 8dee89a8d..fce868c0b 100644 --- a/src/tests/IntegrationTests/BaseIntegrationTest.cs +++ b/src/tests/IntegrationTests/BaseIntegrationTest.cs @@ -13,6 +13,7 @@ class BaseIntegrationTest protected NPath TestBasePath { get; private set; } protected ILogging Logger { get; private set; } public IEnvironment Environment { get; set; } + public IRepository Repository => Environment.Repository; protected TestUtils.SubstituteFactory Factory { get; set; } protected static NPath SolutionDirectory => TestContext.CurrentContext.TestDirectory.ToNPath(); diff --git a/src/tests/IntegrationTests/Events/RepositoryManagerTests.cs b/src/tests/IntegrationTests/Events/RepositoryManagerTests.cs index c8d84ebd2..3e943d875 100644 --- a/src/tests/IntegrationTests/Events/RepositoryManagerTests.cs +++ b/src/tests/IntegrationTests/Events/RepositoryManagerTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using FluentAssertions; using GitHub.Unity; using NSubstitute; @@ -21,6 +22,47 @@ public override void OnSetup() repositoryManagerEvents = new RepositoryManagerEvents(); } + [Test] + public async Task ShouldDoNothingOnInitialize() + { + await Initialize(TestRepoMasterCleanSynchronized); + + var repositoryManagerListener = Substitute.For(); + repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); + + RepositoryManager.WaitForEvents(); + repositoryManagerEvents.WaitForNotBusy(2); + + repositoryManagerListener.AssertDidNotReceiveAnyCalls(); + + Repository.Name.Should().Be("IOTestsRepo"); + Repository.CloneUrl.ToString().Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.Owner.Should().Be("EvilStanleyGoldman"); + Repository.LocalPath.Should().Be(TestRepoMasterCleanSynchronized); + Repository.IsGitHub.Should().BeTrue(); + Repository.CurrentBranchName.Should().Be("master"); + Repository.CurrentBranch.HasValue.Should().BeTrue(); + Repository.CurrentBranch.Value.Name.Should().Be("master"); + Repository.CurrentRemote.HasValue.Should().BeTrue(); + Repository.CurrentRemote.Value.Name.Should().Be("origin"); + Repository.CurrentRemote.Value.Url.Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.LocalBranches.Should().BeEquivalentTo(new[] { + new GitBranch("master", "origin/master", true), + new GitBranch("feature/document", "origin/feature/document", false), + new GitBranch("feature/other-feature", "origin/feature/other-feature", false), + }); + Repository.Remotes.Should().BeEquivalentTo(new GitRemote + { + Name = "origin", + Url = "https://github.com/EvilStanleyGoldman/IOTestsRepo.git" + }); + Repository.RemoteBranches.Should().BeEquivalentTo(new[] { + new GitBranch("origin/master", "[None]", false), + new GitBranch("origin/feature/document-2", "[None]", false), + new GitBranch("origin/feature/other-feature", "[None]", false), + }); + } + [Test] public async Task ShouldDetectFileChanges() { @@ -41,22 +83,28 @@ public async Task ShouldDetectFileChanges() }; var result = new GitStatus(); - Environment.Repository.OnStatusUpdated += status => { result = status; }; + Environment.Repository.OnStatusChanged += status => { result = status; }; var foobarTxt = TestRepoMasterCleanSynchronized.Combine("foobar.txt"); foobarTxt.WriteAllText("foobar"); await TaskManager.Wait(); RepositoryManager.WaitForEvents(); - WaitForNotBusy(repositoryManagerEvents, 1); + repositoryManagerEvents.WaitForNotBusy(); + repositoryManagerEvents.WaitForStatusUpdated(); - repositoryManagerListener.Received().OnStatusUpdate(Args.GitStatus); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.Received().OnStatusUpdated(Args.GitStatus); repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); + repositoryManagerListener.DidNotReceive().OnCurrentBranchUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnCurrentRemoteUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnLocalBranchListUpdated(Arg.Any>()); + repositoryManagerListener.DidNotReceive().OnRemoteBranchListUpdated(Arg.Any>(), Arg.Any>>()); + repositoryManagerListener.DidNotReceive().OnLocalBranchUpdated(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchAdded(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchRemoved(Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchAdded(Args.String, Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchRemoved(Args.String, Args.String); result.AssertEqual(expected); } @@ -69,9 +117,10 @@ public async Task ShouldAddAndCommitFiles() var repositoryManagerListener = Substitute.For(); repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); + var expectedLocalBranch = "master"; var expectedAfterChanges = new GitStatus { Behind = 1, - LocalBranch = "master", + LocalBranch = expectedLocalBranch, RemoteBranch = "origin/master", Entries = new List { @@ -91,17 +140,30 @@ public async Task ShouldAddAndCommitFiles() var testDocumentTxt = TestRepoMasterCleanSynchronized.Combine("Assets", "TestDocument.txt"); testDocumentTxt.WriteAllText("foobar"); + await TaskManager.Wait(); + + //Intentionally wait two cycles, in case the first cycle did not pick up all events + RepositoryManager.WaitForEvents(); + repositoryManagerEvents.WaitForNotBusy(); + repositoryManagerEvents.WaitForStatusUpdated(); + RepositoryManager.WaitForEvents(); - WaitForNotBusy(repositoryManagerEvents, 1); - - repositoryManagerListener.Received().OnStatusUpdate(Args.GitStatus); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); - repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); + repositoryManagerEvents.WaitForNotBusy(); + repositoryManagerEvents.WaitForStatusUpdated(); + + repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.Received().OnStatusUpdated(Args.GitStatus); repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); + repositoryManagerListener.DidNotReceive().OnCurrentBranchUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnCurrentRemoteUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnLocalBranchListUpdated(Arg.Any>()); + repositoryManagerListener.DidNotReceive().OnRemoteBranchListUpdated(Arg.Any>(), Arg.Any>>()); + repositoryManagerListener.DidNotReceive().OnLocalBranchUpdated(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchAdded(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchRemoved(Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchAdded(Args.String, Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchRemoved(Args.String, Args.String); result.AssertEqual(expectedAfterChanges); @@ -109,21 +171,25 @@ public async Task ShouldAddAndCommitFiles() repositoryManagerEvents.Reset(); await RepositoryManager - .CommitFiles(new List() { "Assets\\TestDocument.txt", "foobar.txt" }, "IntegrationTest Commit", string.Empty) + .CommitFiles(new List { "Assets\\TestDocument.txt", "foobar.txt" }, "IntegrationTest Commit", string.Empty) .StartAsAsync(); await TaskManager.Wait(); RepositoryManager.WaitForEvents(); - WaitForNotBusy(repositoryManagerEvents, 1); - repositoryManagerEvents.OnStatusUpdate.WaitOne(TimeSpan.FromSeconds(1)); - - repositoryManagerListener.Received().OnStatusUpdate(Args.GitStatus); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); + repositoryManagerEvents.WaitForNotBusy(); + repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.DidNotReceive().OnStatusUpdated(Args.GitStatus); repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); + repositoryManagerListener.DidNotReceive().OnCurrentBranchUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnCurrentRemoteUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnLocalBranchListUpdated(Arg.Any>()); + repositoryManagerListener.DidNotReceive().OnRemoteBranchListUpdated(Arg.Any>(), Arg.Any>>()); + repositoryManagerListener.Received().OnLocalBranchUpdated(expectedLocalBranch); + repositoryManagerListener.DidNotReceive().OnLocalBranchAdded(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchRemoved(Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchAdded(Args.String, Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchRemoved(Args.String, Args.String); } [Test] @@ -134,9 +200,10 @@ public async Task ShouldAddAndCommitAllFiles() var repositoryManagerListener = Substitute.For(); repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); + var expectedLocalBranch = "master"; var expectedAfterChanges = new GitStatus { Behind = 1, - LocalBranch = "master", + LocalBranch = expectedLocalBranch, RemoteBranch = "origin/master", Entries = new List { @@ -156,17 +223,24 @@ public async Task ShouldAddAndCommitAllFiles() var testDocumentTxt = TestRepoMasterCleanSynchronized.Combine("Assets", "TestDocument.txt"); testDocumentTxt.WriteAllText("foobar"); + await TaskManager.Wait(); - WaitForNotBusy(repositoryManagerEvents, 1); RepositoryManager.WaitForEvents(); + repositoryManagerEvents.WaitForNotBusy(); + repositoryManagerEvents.WaitForStatusUpdated(); - repositoryManagerListener.Received().OnStatusUpdate(Args.GitStatus); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); - repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.Received().OnStatusUpdated(Args.GitStatus); repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); + repositoryManagerListener.DidNotReceive().OnCurrentBranchUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnCurrentRemoteUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnLocalBranchListUpdated(Arg.Any>()); + repositoryManagerListener.DidNotReceive().OnRemoteBranchListUpdated(Arg.Any>(), Arg.Any>>()); + repositoryManagerListener.DidNotReceive().OnLocalBranchUpdated(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchAdded(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchRemoved(Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchAdded(Args.String, Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchRemoved(Args.String, Args.String); result.AssertEqual(expectedAfterChanges); @@ -178,16 +252,21 @@ await RepositoryManager .StartAsAsync(); await TaskManager.Wait(); - WaitForNotBusy(repositoryManagerEvents, 1); RepositoryManager.WaitForEvents(); + repositoryManagerEvents.WaitForNotBusy(); - repositoryManagerListener.DidNotReceive().OnStatusUpdate(Args.GitStatus); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); - repositoryManagerListener.Received(2).OnIsBusyChanged(Args.Bool); + repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.DidNotReceive().OnStatusUpdated(Args.GitStatus); repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); + repositoryManagerListener.DidNotReceive().OnCurrentBranchUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnCurrentRemoteUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnLocalBranchListUpdated(Arg.Any>()); + repositoryManagerListener.DidNotReceive().OnRemoteBranchListUpdated(Arg.Any>(), Arg.Any>>()); + repositoryManagerListener.Received().OnLocalBranchUpdated(expectedLocalBranch); + repositoryManagerListener.DidNotReceive().OnLocalBranchAdded(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchRemoved(Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchAdded(Args.String, Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchRemoved(Args.String, Args.String); } [Test] @@ -198,8 +277,9 @@ public async Task ShouldDetectBranchChange() var repositoryManagerListener = Substitute.For(); repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); + var expectedLocalBranch = "feature/document"; var expected = new GitStatus { - LocalBranch = "feature/document", + LocalBranch = expectedLocalBranch, RemoteBranch = "origin/feature/document", Entries = new List() }; @@ -207,21 +287,55 @@ public async Task ShouldDetectBranchChange() var result = new GitStatus(); RepositoryManager.OnStatusUpdated += status => { result = status; }; - await RepositoryManager.SwitchBranch("feature/document").StartAsAsync(); + Logger.Trace("Starting test"); + + await RepositoryManager.SwitchBranch(expectedLocalBranch).StartAsAsync(); + await TaskManager.Wait(); RepositoryManager.WaitForEvents(); - WaitForNotBusy(repositoryManagerEvents, 5); - repositoryManagerEvents.OnStatusUpdate.WaitOne(TimeSpan.FromSeconds(1)); - - repositoryManagerListener.Received().OnStatusUpdate(Args.GitStatus); - repositoryManagerListener.Received().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.Received().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); + repositoryManagerEvents.WaitForNotBusy(); + repositoryManagerEvents.WaitForStatusUpdated(); + repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.Received().OnStatusUpdated(Args.GitStatus); repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); + repositoryManagerListener.Received().OnCurrentBranchUpdated(Arg.Any()); + repositoryManagerListener.Received().OnCurrentRemoteUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnLocalBranchListUpdated(Arg.Any>()); + repositoryManagerListener.DidNotReceive().OnRemoteBranchListUpdated(Arg.Any>(), Arg.Any>>()); + repositoryManagerListener.DidNotReceive().OnLocalBranchUpdated(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchAdded(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchRemoved(Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchAdded(Args.String, Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchRemoved(Args.String, Args.String); result.AssertEqual(expected); + + Repository.Name.Should().Be("IOTestsRepo"); + Repository.CloneUrl.ToString().Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.Owner.Should().Be("EvilStanleyGoldman"); + Repository.LocalPath.Should().Be(TestRepoMasterCleanSynchronized); + Repository.IsGitHub.Should().BeTrue(); + Repository.CurrentBranchName.Should().Be("feature/document"); + Repository.CurrentBranch.HasValue.Should().BeTrue(); + Repository.CurrentBranch.Value.Name.Should().Be("feature/document"); + Repository.CurrentRemote.HasValue.Should().BeTrue(); + Repository.CurrentRemote.Value.Name.Should().Be("origin"); + Repository.CurrentRemote.Value.Url.Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.LocalBranches.Should().BeEquivalentTo(new[] { + new GitBranch("master", "origin/master", false), + new GitBranch("feature/document", "origin/feature/document", true), + new GitBranch("feature/other-feature", "origin/feature/other-feature", false), + }); + Repository.Remotes.Should().BeEquivalentTo(new GitRemote { + Name = "origin", + Url = "https://github.com/EvilStanleyGoldman/IOTestsRepo.git" + }); + Repository.RemoteBranches.Should().BeEquivalentTo(new[] { + new GitBranch("origin/master", "[None]", false), + new GitBranch("origin/feature/document-2", "[None]", false), + new GitBranch("origin/feature/other-feature", "[None]", false), + }); } [Test] @@ -232,19 +346,50 @@ public async Task ShouldDetectBranchDelete() var repositoryManagerListener = Substitute.For(); repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); - await RepositoryManager.DeleteBranch("feature/document", true).StartAsAsync(); + var deletedBranch = "feature/document"; + await RepositoryManager.DeleteBranch(deletedBranch, true).StartAsAsync(); await TaskManager.Wait(); - WaitForNotBusy(repositoryManagerEvents, 1); RepositoryManager.WaitForEvents(); + repositoryManagerEvents.WaitForNotBusy(); - repositoryManagerListener.DidNotReceive().OnStatusUpdate(Args.GitStatus); - //TODO: Deleting a branch causes a config reload, which raises OnActiveBranchChanged/OnActiveRemoteChanged - repositoryManagerListener.Received().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.Received().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.Received(1).OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); - repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.DidNotReceive().OnStatusUpdated(Args.GitStatus); repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); + repositoryManagerListener.Received().OnCurrentBranchUpdated(Arg.Any()); + repositoryManagerListener.Received().OnCurrentRemoteUpdated(Arg.Any()); + repositoryManagerListener.Received().OnLocalBranchListUpdated(Arg.Any>()); + repositoryManagerListener.Received().OnRemoteBranchListUpdated(Arg.Any>(), Arg.Any>>()); + repositoryManagerListener.DidNotReceive().OnLocalBranchUpdated(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchAdded(Args.String); + repositoryManagerListener.Received().OnLocalBranchRemoved(deletedBranch); + repositoryManagerListener.DidNotReceive().OnRemoteBranchAdded(Args.String, Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchRemoved(Args.String, Args.String); + + Repository.Name.Should().Be("IOTestsRepo"); + Repository.CloneUrl.ToString().Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.Owner.Should().Be("EvilStanleyGoldman"); + Repository.LocalPath.Should().Be(TestRepoMasterCleanSynchronized); + Repository.IsGitHub.Should().BeTrue(); + Repository.CurrentBranchName.Should().Be("master"); + Repository.CurrentBranch.HasValue.Should().BeTrue(); + Repository.CurrentBranch.Value.Name.Should().Be("master"); + Repository.CurrentRemote.HasValue.Should().BeTrue(); + Repository.CurrentRemote.Value.Name.Should().Be("origin"); + Repository.CurrentRemote.Value.Url.Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.LocalBranches.Should().BeEquivalentTo(new[] { + new GitBranch("master", "origin/master", true), + new GitBranch("feature/other-feature", "origin/feature/other-feature", false), + }); + Repository.Remotes.Should().BeEquivalentTo(new GitRemote + { + Name = "origin", + Url = "https://github.com/EvilStanleyGoldman/IOTestsRepo.git" + }); + Repository.RemoteBranches.Should().BeEquivalentTo(new[] { + new GitBranch("origin/master", "[None]", false), + new GitBranch("origin/feature/document-2", "[None]", false), + new GitBranch("origin/feature/other-feature", "[None]", false), + }); } [Test] @@ -255,32 +400,103 @@ public async Task ShouldDetectBranchCreate() var repositoryManagerListener = Substitute.For(); repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); - await RepositoryManager.CreateBranch("feature/document2", "feature/document").StartAsAsync(); + var createdBranch1 = "feature/document2"; + await RepositoryManager.CreateBranch(createdBranch1, "feature/document").StartAsAsync(); await TaskManager.Wait(); RepositoryManager.WaitForEvents(); + repositoryManagerEvents.WaitForNotBusy(); - repositoryManagerListener.DidNotReceive().OnStatusUpdate(Args.GitStatus); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.Received(1).OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); - repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.DidNotReceive().OnStatusUpdated(Args.GitStatus); repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); + repositoryManagerListener.DidNotReceive().OnCurrentBranchUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnCurrentRemoteUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnLocalBranchListUpdated(Arg.Any>()); + repositoryManagerListener.DidNotReceive().OnRemoteBranchListUpdated(Arg.Any>(), Arg.Any>>()); + repositoryManagerListener.DidNotReceive().OnLocalBranchUpdated(Args.String); + repositoryManagerListener.Received().OnLocalBranchAdded(createdBranch1); + repositoryManagerListener.DidNotReceive().OnLocalBranchRemoved(Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchAdded(Args.String, Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchRemoved(Args.String, Args.String); + + Repository.Name.Should().Be("IOTestsRepo"); + Repository.CloneUrl.ToString().Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.Owner.Should().Be("EvilStanleyGoldman"); + Repository.LocalPath.Should().Be(TestRepoMasterCleanSynchronized); + Repository.IsGitHub.Should().BeTrue(); + Repository.CurrentBranchName.Should().Be("master"); + Repository.CurrentBranch.HasValue.Should().BeTrue(); + Repository.CurrentBranch.Value.Name.Should().Be("master"); + Repository.CurrentRemote.HasValue.Should().BeTrue(); + Repository.CurrentRemote.Value.Name.Should().Be("origin"); + Repository.CurrentRemote.Value.Url.Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.LocalBranches.Should().BeEquivalentTo(new[] { + new GitBranch("master", "origin/master", true), + new GitBranch("feature/document", "origin/feature/document", false), + new GitBranch("feature/document2", "[None]", false), + new GitBranch("feature/other-feature", "origin/feature/other-feature", false), + }); + Repository.Remotes.Should().BeEquivalentTo(new GitRemote + { + Name = "origin", + Url = "https://github.com/EvilStanleyGoldman/IOTestsRepo.git" + }); + Repository.RemoteBranches.Should().BeEquivalentTo(new[] { + new GitBranch("origin/master", "[None]", false), + new GitBranch("origin/feature/document-2", "[None]", false), + new GitBranch("origin/feature/other-feature", "[None]", false), + }); repositoryManagerListener.ClearReceivedCalls(); repositoryManagerEvents.Reset(); - await RepositoryManager.CreateBranch("feature2/document2", "feature/document").StartAsAsync(); + var createdBranch2 = "feature2/document2"; + await RepositoryManager.CreateBranch(createdBranch2, "feature/document").StartAsAsync(); await TaskManager.Wait(); RepositoryManager.WaitForEvents(); + repositoryManagerEvents.WaitForNotBusy(); - repositoryManagerListener.DidNotReceive().OnStatusUpdate(Args.GitStatus); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.Received(1).OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); - repositoryManagerListener.Received(2).OnIsBusyChanged(Args.Bool); + repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.DidNotReceive().OnStatusUpdated(Args.GitStatus); repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); + repositoryManagerListener.DidNotReceive().OnCurrentBranchUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnCurrentRemoteUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnLocalBranchListUpdated(Arg.Any>()); + repositoryManagerListener.DidNotReceive().OnRemoteBranchListUpdated(Arg.Any>(), Arg.Any>>()); + repositoryManagerListener.DidNotReceive().OnLocalBranchUpdated(Args.String); + repositoryManagerListener.Received().OnLocalBranchAdded(createdBranch2); + repositoryManagerListener.DidNotReceive().OnLocalBranchRemoved(Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchAdded(Args.String, Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchRemoved(Args.String, Args.String); + + Repository.Name.Should().Be("IOTestsRepo"); + Repository.CloneUrl.ToString().Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.Owner.Should().Be("EvilStanleyGoldman"); + Repository.LocalPath.Should().Be(TestRepoMasterCleanSynchronized); + Repository.IsGitHub.Should().BeTrue(); + Repository.CurrentBranchName.Should().Be("master"); + Repository.CurrentBranch.HasValue.Should().BeTrue(); + Repository.CurrentBranch.Value.Name.Should().Be("master"); + Repository.CurrentRemote.HasValue.Should().BeTrue(); + Repository.CurrentRemote.Value.Name.Should().Be("origin"); + Repository.CurrentRemote.Value.Url.Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.LocalBranches.Should().BeEquivalentTo(new[] { + new GitBranch("master", "origin/master", true), + new GitBranch("feature/document", "origin/feature/document", false), + new GitBranch("feature/document2", "[None]", false), + new GitBranch("feature2/document2", "[None]", false), + new GitBranch("feature/other-feature", "origin/feature/other-feature", false), + }); + Repository.Remotes.Should().BeEquivalentTo(new GitRemote + { + Name = "origin", + Url = "https://github.com/EvilStanleyGoldman/IOTestsRepo.git" + }); + Repository.RemoteBranches.Should().BeEquivalentTo(new[] { + new GitBranch("origin/master", "[None]", false), + new GitBranch("origin/feature/document-2", "[None]", false), + new GitBranch("origin/feature/other-feature", "[None]", false), + }); } [Test] @@ -291,29 +507,71 @@ public async Task ShouldDetectChangesToRemotes() var repositoryManagerListener = Substitute.For(); repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); - Environment.Repository.CurrentRemote.HasValue.Should().BeTrue(); - Environment.Repository.CurrentRemote.Value.Name.Should().Be("origin"); - Environment.Repository.CurrentRemote.Value.Url.Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); - - Environment.Repository.CloneUrl.Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); - Environment.Repository.Owner.Should().Be("EvilStanleyGoldman"); + RepositoryManager.WaitForEvents(); + repositoryManagerEvents.WaitForNotBusy(2); + + repositoryManagerListener.AssertDidNotReceiveAnyCalls(); + + Repository.Name.Should().Be("IOTestsRepo"); + Repository.CloneUrl.ToString().Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.Owner.Should().Be("EvilStanleyGoldman"); + Repository.LocalPath.Should().Be(TestRepoMasterCleanSynchronized); + Repository.IsGitHub.Should().BeTrue(); + Repository.CurrentBranchName.Should().Be("master"); + Repository.CurrentBranch.HasValue.Should().BeTrue(); + Repository.CurrentBranch.Value.Name.Should().Be("master"); + Repository.CurrentRemote.HasValue.Should().BeTrue(); + Repository.CurrentRemote.Value.Name.Should().Be("origin"); + Repository.CurrentRemote.Value.Url.Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.LocalBranches.Should().BeEquivalentTo(new[] { + new GitBranch("master", "origin/master", true), + new GitBranch("feature/document", "origin/feature/document", false), + new GitBranch("feature/other-feature", "origin/feature/other-feature", false), + }); + Repository.Remotes.Should().BeEquivalentTo(new GitRemote + { + Name = "origin", + Url = "https://github.com/EvilStanleyGoldman/IOTestsRepo.git" + }); + Repository.RemoteBranches.Should().BeEquivalentTo(new[] { + new GitBranch("origin/master", "[None]", false), + new GitBranch("origin/feature/document-2", "[None]", false), + new GitBranch("origin/feature/other-feature", "[None]", false), + }); await RepositoryManager.RemoteRemove("origin").StartAsAsync(); await TaskManager.Wait(); - RepositoryManager.WaitForEvents(); - Environment.Repository.CurrentRemote.HasValue.Should().BeFalse(); + RepositoryManager.WaitForEvents(); + repositoryManagerEvents.WaitForNotBusy(); - Environment.Repository.CloneUrl.Should().BeNull(); - Environment.Repository.Owner.Should().BeNull(); + repositoryManagerEvents.OnRemoteBranchListUpdated.WaitOne(TimeSpan.FromSeconds(1)); + repositoryManagerEvents.OnLocalBranchListUpdated.WaitOne(TimeSpan.FromSeconds(1)); - repositoryManagerListener.DidNotReceive().OnStatusUpdate(Args.GitStatus); - repositoryManagerListener.Received().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.Received().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.Received().OnRemoteBranchListChanged(); - repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.DidNotReceive().OnStatusUpdated(Args.GitStatus); repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); + repositoryManagerListener.Received().OnCurrentBranchUpdated(Arg.Any()); + repositoryManagerListener.Received().OnCurrentRemoteUpdated(Arg.Any()); + repositoryManagerListener.Received().OnLocalBranchListUpdated(Arg.Any>()); + repositoryManagerListener.Received().OnRemoteBranchListUpdated(Arg.Any>(), Arg.Any>>()); + repositoryManagerListener.DidNotReceive().OnLocalBranchUpdated(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchAdded(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchRemoved(Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchAdded(Args.String, Args.String); + repositoryManagerListener.Received().OnRemoteBranchRemoved(Args.String, Args.String); + + Repository.Name.Should().Be("IOTestsRepo_master_clean_sync"); + Repository.CloneUrl.Should().BeNull(); + Repository.Owner.Should().BeNull(); + Repository.LocalPath.Should().Be(TestRepoMasterCleanSynchronized); + Repository.IsGitHub.Should().BeFalse(); + Repository.CurrentBranchName.Should().Be("master"); + Repository.CurrentBranch.HasValue.Should().BeTrue(); + Repository.CurrentBranch.Value.Name.Should().Be("master"); + Repository.CurrentRemote.HasValue.Should().BeFalse(); + Repository.Remotes.Should().BeEquivalentTo(); + Repository.RemoteBranches.Should().BeEmpty(); repositoryManagerListener.ClearReceivedCalls(); repositoryManagerEvents.Reset(); @@ -321,52 +579,148 @@ public async Task ShouldDetectChangesToRemotes() await RepositoryManager.RemoteAdd("origin", "https://github.com/EvilShana/IOTestsRepo.git").StartAsAsync(); await TaskManager.Wait(); RepositoryManager.WaitForEvents(); + repositoryManagerEvents.WaitForNotBusy(); + repositoryManagerEvents.OnRemoteBranchListUpdated.WaitOne(TimeSpan.FromSeconds(1)); + repositoryManagerEvents.OnLocalBranchListUpdated.WaitOne(TimeSpan.FromSeconds(1)); - Environment.Repository.CurrentRemote.HasValue.Should().BeTrue(); - Environment.Repository.CurrentRemote.Value.Name.Should().Be("origin"); - Environment.Repository.CurrentRemote.Value.Url.Should().Be("https://github.com/EvilShana/IOTestsRepo.git"); - - Environment.Repository.CloneUrl.Should().Be("https://github.com/EvilShana/IOTestsRepo.git"); - Environment.Repository.Owner.Should().Be("EvilShana"); - - repositoryManagerListener.DidNotReceive().OnStatusUpdate(Args.GitStatus); - repositoryManagerListener.Received().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.Received().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); - repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.DidNotReceive().OnStatusUpdated(Args.GitStatus); repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); + repositoryManagerListener.Received().OnCurrentBranchUpdated(Arg.Any()); + repositoryManagerListener.Received().OnCurrentRemoteUpdated(Arg.Any()); + repositoryManagerListener.Received().OnLocalBranchListUpdated(Arg.Any>()); + repositoryManagerListener.Received().OnRemoteBranchListUpdated(Arg.Any>(), Arg.Any>>()); + repositoryManagerListener.DidNotReceive().OnLocalBranchUpdated(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchAdded(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchRemoved(Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchAdded(Args.String, Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchRemoved(Args.String, Args.String); + + Repository.Name.Should().Be("IOTestsRepo"); + Repository.CloneUrl.ToString().Should().Be("https://github.com/EvilShana/IOTestsRepo.git"); + Repository.Owner.Should().Be("EvilShana"); + Repository.LocalPath.Should().Be(TestRepoMasterCleanSynchronized); + Repository.IsGitHub.Should().BeTrue(); + Repository.CurrentBranchName.Should().Be("master"); + Repository.CurrentBranch.HasValue.Should().BeTrue(); + Repository.CurrentBranch.Value.Name.Should().Be("master"); + Repository.CurrentRemote.HasValue.Should().BeTrue(); + Repository.CurrentRemote.Value.Name.Should().Be("origin"); + Repository.CurrentRemote.Value.Url.Should().Be("https://github.com/EvilShana/IOTestsRepo.git"); + Repository.LocalBranches.Should().BeEquivalentTo(new[] { + new GitBranch("master", "[None]", true), + new GitBranch("feature/document", "[None]", false), + new GitBranch("feature/other-feature", "[None]", false), + }); + Repository.Remotes.Should().BeEquivalentTo(new GitRemote + { + Name = "origin", + Url = "https://github.com/EvilShana/IOTestsRepo.git" + }); + Repository.RemoteBranches.Should().BeEmpty(); } [Test] public async Task ShouldDetectChangesToRemotesWhenSwitchingBranches() { - var expectedCloneUrl = "https://github.com/EvilStanleyGoldman/IOTestsRepo.git"; - await Initialize(TestRepoMasterTwoRemotes); var repositoryManagerListener = Substitute.For(); repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); - Environment.Repository.CurrentRemote.HasValue.Should().BeTrue(); - Environment.Repository.CurrentRemote.Value.Name.Should().Be("origin"); - Environment.Repository.CurrentRemote.Value.Url.Should().Be(expectedCloneUrl); - Environment.Repository.Owner.Should().Be("EvilStanleyGoldman"); + RepositoryManager.WaitForEvents(); + repositoryManagerEvents.WaitForNotBusy(2); + + repositoryManagerListener.AssertDidNotReceiveAnyCalls(); + + Repository.Name.Should().Be("IOTestsRepo"); + Repository.CloneUrl.ToString().Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.Owner.Should().Be("EvilStanleyGoldman"); + Repository.LocalPath.Should().Be(TestRepoMasterTwoRemotes); + Repository.IsGitHub.Should().BeTrue(); + Repository.CurrentBranchName.Should().Be("master"); + Repository.CurrentBranch.HasValue.Should().BeTrue(); + Repository.CurrentBranch.Value.Name.Should().Be("master"); + Repository.CurrentRemote.HasValue.Should().BeTrue(); + Repository.CurrentRemote.Value.Name.Should().Be("origin"); + Repository.CurrentRemote.Value.Url.Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.LocalBranches.Should().BeEquivalentTo(new[] { + new GitBranch("master", "origin/master", true), + new GitBranch("feature/document", "origin/feature/document", false), + new GitBranch("feature/other-feature", "origin/feature/other-feature", false), + }); + Repository.Remotes.Should().BeEquivalentTo(new GitRemote + { + Name = "origin", + Url = "https://github.com/EvilStanleyGoldman/IOTestsRepo.git" + }, new GitRemote + { + Name = "another", + Url = "https://another.remote/Owner/Url.git" + }); + Repository.RemoteBranches.Should().BeEquivalentTo(new[] { + new GitBranch("origin/master", "[None]", false), + new GitBranch("origin/feature/document-2", "[None]", false), + new GitBranch("origin/feature/other-feature", "[None]", false), + new GitBranch("another/master", "[None]", false), + new GitBranch("another/feature/document-2", "[None]", false), + new GitBranch("another/feature/other-feature", "[None]", false), + }); await RepositoryManager.CreateBranch("branch2", "another/master") - //.Then(RepositoryManager.SwitchBranch("branch2")) .StartAsAsync(); await TaskManager.Wait(); RepositoryManager.WaitForEvents(); + repositoryManagerEvents.WaitForNotBusy(); - repositoryManagerListener.DidNotReceive().OnStatusUpdate(Args.GitStatus); - repositoryManagerListener.Received().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.Received().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.Received().OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); - repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.DidNotReceive().OnStatusUpdated(Args.GitStatus); repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); + repositoryManagerListener.Received().OnCurrentBranchUpdated(Arg.Any()); + repositoryManagerListener.Received().OnCurrentRemoteUpdated(Arg.Any()); + repositoryManagerListener.Received().OnLocalBranchListUpdated(Arg.Any>()); + repositoryManagerListener.Received().OnRemoteBranchListUpdated(Arg.Any>(), Arg.Any>>()); + repositoryManagerListener.DidNotReceive().OnLocalBranchUpdated(Args.String); + repositoryManagerListener.Received().OnLocalBranchAdded(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchRemoved(Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchAdded(Args.String, Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchRemoved(Args.String, Args.String); + + Repository.Name.Should().Be("IOTestsRepo"); + Repository.CloneUrl.ToString().Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.Owner.Should().Be("EvilStanleyGoldman"); + Repository.LocalPath.Should().Be(TestRepoMasterTwoRemotes); + Repository.IsGitHub.Should().BeTrue(); + Repository.CurrentBranchName.Should().Be("master"); + Repository.CurrentBranch.HasValue.Should().BeTrue(); + Repository.CurrentBranch.Value.Name.Should().Be("master"); + Repository.CurrentRemote.HasValue.Should().BeTrue(); + Repository.CurrentRemote.Value.Name.Should().Be("origin"); + Repository.CurrentRemote.Value.Url.Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.LocalBranches.Should().BeEquivalentTo(new[] { + new GitBranch("master", "origin/master", true), + new GitBranch("branch2", "another/branch2", false), + new GitBranch("feature/document", "origin/feature/document", false), + new GitBranch("feature/other-feature", "origin/feature/other-feature", false), + }); + Repository.Remotes.Should().BeEquivalentTo(new GitRemote + { + Name = "origin", + Url = "https://github.com/EvilStanleyGoldman/IOTestsRepo.git" + }, new GitRemote + { + Name = "another", + Url = "https://another.remote/Owner/Url.git" + }); + Repository.RemoteBranches.Should().BeEquivalentTo(new[] { + new GitBranch("origin/master", "[None]", false), + new GitBranch("origin/feature/document-2", "[None]", false), + new GitBranch("origin/feature/other-feature", "[None]", false), + new GitBranch("another/master", "[None]", false), + new GitBranch("another/feature/document-2", "[None]", false), + new GitBranch("another/feature/other-feature", "[None]", false), + }); repositoryManagerListener.ClearReceivedCalls(); repositoryManagerEvents.Reset(); @@ -376,76 +730,56 @@ await RepositoryManager.SwitchBranch("branch2") await TaskManager.Wait(); RepositoryManager.WaitForEvents(); + repositoryManagerEvents.WaitForNotBusy(); + repositoryManagerEvents.WaitForHeadUpdated(); - Environment.Repository.CurrentRemote.HasValue.Should().BeTrue(); - Environment.Repository.CurrentRemote.Value.Name.Should().Be("another"); - - var expectedRemoteUrl = "https://another.remote/Owner/Url.git"; - Environment.Repository.CurrentRemote.Value.Url.Should().Be(expectedRemoteUrl); - Environment.Repository.CloneUrl.ToString().Should().Be(expectedRemoteUrl); - Environment.Repository.Owner.Should().Be("Owner"); - - repositoryManagerListener.DidNotReceive().OnStatusUpdate(Args.GitStatus); - repositoryManagerListener.Received().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.Received().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.Received().OnStatusUpdated(Args.GitStatus); repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); - } - - [Test] - public async Task ShouldUpdateCloneUrlIfRemoteIsDeleted() - { - await Initialize(TestRepoMasterCleanSynchronized); - - var repositoryManagerListener = Substitute.For(); - repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); - - Environment.Repository.CurrentRemote.HasValue.Should().BeTrue(); - Environment.Repository.CurrentRemote.Value.Name.Should().Be("origin"); - Environment.Repository.CurrentRemote.Value.Url.Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); - - Environment.Repository.CloneUrl.Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); - Environment.Repository.Owner.Should().Be("EvilStanleyGoldman"); - - await RepositoryManager.RemoteRemove("origin").StartAsAsync(); - await TaskManager.Wait(); - RepositoryManager.WaitForEvents(); - - Environment.Repository.CurrentRemote.HasValue.Should().BeFalse(); - - Environment.Repository.CloneUrl.Should().BeNull(); - Environment.Repository.Owner.Should().BeNull(); - - repositoryManagerListener.DidNotReceive().OnStatusUpdate(Args.GitStatus); - repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.Received().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.Received().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.Received().OnRemoteBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); - - repositoryManagerListener.ClearReceivedCalls(); - repositoryManagerEvents.Reset(); - - await RepositoryManager.RemoteAdd("origin", "https://github.com/EvilShana/IOTestsRepo.git").StartAsAsync(); - await TaskManager.Wait(); - RepositoryManager.WaitForEvents(); - - Environment.Repository.CurrentRemote.HasValue.Should().BeTrue(); - Environment.Repository.CurrentRemote.Value.Url.Should().Be("https://github.com/EvilShana/IOTestsRepo.git"); - - Environment.Repository.CloneUrl.Should().Be("https://github.com/EvilShana/IOTestsRepo.git"); - Environment.Repository.Owner.Should().Be("EvilShana"); - - repositoryManagerListener.DidNotReceive().OnStatusUpdate(Args.GitStatus); - repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.Received().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.Received().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); + repositoryManagerListener.Received().OnCurrentBranchUpdated(Arg.Any()); + repositoryManagerListener.Received().OnCurrentRemoteUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnLocalBranchListUpdated(Arg.Any>()); + repositoryManagerListener.DidNotReceive().OnRemoteBranchListUpdated(Arg.Any>(), Arg.Any>>()); + repositoryManagerListener.DidNotReceive().OnLocalBranchUpdated(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchAdded(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchRemoved(Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchAdded(Args.String, Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchRemoved(Args.String, Args.String); + + Repository.Name.Should().Be("Url"); + Repository.CloneUrl.ToString().Should().Be("https://another.remote/Owner/Url.git"); + Repository.Owner.Should().Be("Owner"); + Repository.LocalPath.Should().Be(TestRepoMasterTwoRemotes); + Repository.IsGitHub.Should().BeFalse(); + Repository.CurrentBranchName.Should().Be("branch2"); + Repository.CurrentBranch.HasValue.Should().BeTrue(); + Repository.CurrentBranch.Value.Name.Should().Be("branch2"); + Repository.CurrentRemote.HasValue.Should().BeTrue(); + Repository.CurrentRemote.Value.Name.Should().Be("another"); + Repository.CurrentRemote.Value.Url.Should().Be("https://another.remote/Owner/Url.git"); + Repository.LocalBranches.Should().BeEquivalentTo(new[] { + new GitBranch("master", "origin/master", false), + new GitBranch("branch2", "another/branch2", true), + new GitBranch("feature/document", "origin/feature/document", false), + new GitBranch("feature/other-feature", "origin/feature/other-feature", false), + }); + Repository.Remotes.Should().BeEquivalentTo(new GitRemote + { + Name = "origin", + Url = "https://github.com/EvilStanleyGoldman/IOTestsRepo.git" + }, new GitRemote + { + Name = "another", + Url = "https://another.remote/Owner/Url.git" + }); + Repository.RemoteBranches.Should().BeEquivalentTo(new[] { + new GitBranch("origin/master", "[None]", false), + new GitBranch("origin/feature/document-2", "[None]", false), + new GitBranch("origin/feature/other-feature", "[None]", false), + new GitBranch("another/master", "[None]", false), + new GitBranch("another/feature/document-2", "[None]", false), + new GitBranch("another/feature/other-feature", "[None]", false), + }); } [Test] @@ -468,19 +802,53 @@ public async Task ShouldDetectGitPull() await RepositoryManager.Pull("origin", "master").StartAsAsync(); await TaskManager.Wait(); RepositoryManager.WaitForEvents(); + repositoryManagerEvents.WaitForNotBusy(); + repositoryManagerEvents.WaitForStatusUpdated(); - repositoryManagerListener.Received().OnStatusUpdate(Args.GitStatus); - repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); + repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.Received().OnStatusUpdated(Args.GitStatus); repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); + repositoryManagerListener.DidNotReceive().OnCurrentBranchUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnCurrentRemoteUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnLocalBranchListUpdated(Arg.Any>()); + repositoryManagerListener.DidNotReceive().OnRemoteBranchListUpdated(Arg.Any>(), Arg.Any>>()); + repositoryManagerListener.Received().OnLocalBranchUpdated(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchAdded(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchRemoved(Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchAdded(Args.String, Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchRemoved(Args.String, Args.String); result.AssertEqual(expected); + Repository.Name.Should().Be("IOTestsRepo"); + Repository.CloneUrl.ToString().Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.Owner.Should().Be("EvilStanleyGoldman"); + Repository.LocalPath.Should().Be(TestRepoMasterCleanSynchronized); + Repository.IsGitHub.Should().BeTrue(); + Repository.CurrentBranchName.Should().Be("master"); + Repository.CurrentBranch.HasValue.Should().BeTrue(); + Repository.CurrentBranch.Value.Name.Should().Be("master"); + Repository.CurrentRemote.HasValue.Should().BeTrue(); + Repository.CurrentRemote.Value.Name.Should().Be("origin"); + Repository.CurrentRemote.Value.Url.Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.LocalBranches.Should().BeEquivalentTo(new[] { + new GitBranch("master", "origin/master", true), + new GitBranch("feature/document", "origin/feature/document", false), + new GitBranch("feature/other-feature", "origin/feature/other-feature", false), + }); + Repository.Remotes.Should().BeEquivalentTo(new GitRemote + { + Name = "origin", + Url = "https://github.com/EvilStanleyGoldman/IOTestsRepo.git" + }); + Repository.RemoteBranches.Should().BeEquivalentTo(new[] { + new GitBranch("origin/master", "[None]", false), + new GitBranch("origin/feature/document-2", "[None]", false), + new GitBranch("origin/feature/other-feature", "[None]", false), + }); + repositoryManagerEvents.Reset(); - WaitForNotBusy(repositoryManagerEvents, 1); + repositoryManagerEvents.WaitForNotBusy(); } [Test] @@ -491,23 +859,80 @@ public async Task ShouldDetectGitFetch() var repositoryManagerListener = Substitute.For(); repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); + RepositoryManager.WaitForEvents(); + repositoryManagerEvents.WaitForNotBusy(2); + + repositoryManagerListener.AssertDidNotReceiveAnyCalls(); + + Repository.Name.Should().Be("IOTestsRepo"); + Repository.CloneUrl.ToString().Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.Owner.Should().Be("EvilStanleyGoldman"); + Repository.LocalPath.Should().Be(TestRepoMasterCleanUnsynchronized); + Repository.IsGitHub.Should().BeTrue(); + Repository.CurrentBranchName.Should().Be("master"); + Repository.CurrentBranch.HasValue.Should().BeTrue(); + Repository.CurrentBranch.Value.Name.Should().Be("master"); + Repository.CurrentRemote.HasValue.Should().BeTrue(); + Repository.CurrentRemote.Value.Name.Should().Be("origin"); + Repository.CurrentRemote.Value.Url.Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.LocalBranches.Should().BeEquivalentTo(new[] { + new GitBranch("feature/document", "origin/feature/document", false), + }); + Repository.Remotes.Should().BeEquivalentTo(new GitRemote + { + Name = "origin", + Url = "https://github.com/EvilStanleyGoldman/IOTestsRepo.git" + }); + Repository.RemoteBranches.Should().BeEquivalentTo(new[] { + new GitBranch("origin/master", "[None]", false), + new GitBranch("origin/feature/document", "[None]", false), + new GitBranch("origin/feature/document-2", "[None]", false), + }); + await RepositoryManager.Fetch("origin").StartAsAsync(); await TaskManager.Wait(); RepositoryManager.WaitForEvents(); + repositoryManagerEvents.WaitForNotBusy(); - repositoryManagerListener.DidNotReceive().OnStatusUpdate(Args.GitStatus); - repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.Received(2).OnRemoteBranchListChanged(); + repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.DidNotReceive().OnStatusUpdated(Args.GitStatus); repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); - } - - private void WaitForNotBusy(RepositoryManagerEvents managerEvents, int seconds = 1) - { - managerEvents.OnIsBusy.WaitOne(TimeSpan.FromSeconds(seconds)); - managerEvents.OnIsNotBusy.WaitOne(TimeSpan.FromSeconds(seconds)); + repositoryManagerListener.DidNotReceive().OnCurrentBranchUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnCurrentRemoteUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnLocalBranchListUpdated(Arg.Any>()); + repositoryManagerListener.DidNotReceive().OnRemoteBranchListUpdated(Arg.Any>(), Arg.Any>>()); + repositoryManagerListener.DidNotReceive().OnLocalBranchUpdated(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchAdded(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchRemoved(Args.String); + repositoryManagerListener.Received().OnRemoteBranchAdded(Args.String, Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchRemoved(Args.String, Args.String); + + Repository.Name.Should().Be("IOTestsRepo"); + Repository.CloneUrl.ToString().Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.Owner.Should().Be("EvilStanleyGoldman"); + Repository.LocalPath.Should().Be(TestRepoMasterCleanUnsynchronized); + Repository.IsGitHub.Should().BeTrue(); + Repository.CurrentBranchName.Should().Be("master"); + Repository.CurrentBranch.HasValue.Should().BeTrue(); + Repository.CurrentBranch.Value.Name.Should().Be("master"); + Repository.CurrentRemote.HasValue.Should().BeTrue(); + Repository.CurrentRemote.Value.Name.Should().Be("origin"); + Repository.CurrentRemote.Value.Url.Should().Be("https://github.com/EvilStanleyGoldman/IOTestsRepo.git"); + Repository.LocalBranches.Should().BeEquivalentTo(new[] { + new GitBranch("feature/document", "origin/feature/document", false), + }); + Repository.Remotes.Should().BeEquivalentTo(new GitRemote + { + Name = "origin", + Url = "https://github.com/EvilStanleyGoldman/IOTestsRepo.git" + }); + Repository.RemoteBranches.Should().BeEquivalentTo(new[] { + new GitBranch("origin/master", "[None]", false), + new GitBranch("origin/feature/document", "[None]", false), + new GitBranch("origin/feature/document-2", "[None]", false), + new GitBranch("origin/feature/new-feature", "[None]", false), + new GitBranch("origin/feature/other-feature", "[None]", false), + }); } } } diff --git a/src/tests/IntegrationTests/Events/RepositoryWatcherTests.cs b/src/tests/IntegrationTests/Events/RepositoryWatcherTests.cs index f2e1e5447..6427fac6c 100644 --- a/src/tests/IntegrationTests/Events/RepositoryWatcherTests.cs +++ b/src/tests/IntegrationTests/Events/RepositoryWatcherTests.cs @@ -44,7 +44,7 @@ public async Task ShouldDetectFileChanges() Logger.Trace("Continue test"); repositoryWatcherListener.DidNotReceive().ConfigChanged(); - repositoryWatcherListener.DidNotReceive().HeadChanged(Args.String); + repositoryWatcherListener.DidNotReceive().HeadChanged(); repositoryWatcherListener.Received().IndexChanged(); repositoryWatcherListener.DidNotReceive().LocalBranchCreated(Args.String); repositoryWatcherListener.DidNotReceive().LocalBranchDeleted(Args.String); @@ -90,7 +90,7 @@ public async Task ShouldDetectBranchChange() Logger.Trace("Continue test"); repositoryWatcherListener.DidNotReceive().ConfigChanged(); - repositoryWatcherListener.Received(1).HeadChanged("ref: refs/heads/feature/document"); + repositoryWatcherListener.Received().HeadChanged(); repositoryWatcherListener.Received().IndexChanged(); repositoryWatcherListener.DidNotReceive().LocalBranchCreated(Args.String); repositoryWatcherListener.DidNotReceive().LocalBranchDeleted(Args.String); @@ -135,7 +135,7 @@ public async Task ShouldDetectBranchDelete() Logger.Trace("Continue test"); repositoryWatcherListener.Received(1).ConfigChanged(); - repositoryWatcherListener.DidNotReceive().HeadChanged(Args.String); + repositoryWatcherListener.DidNotReceive().HeadChanged(); repositoryWatcherListener.DidNotReceive().IndexChanged(); repositoryWatcherListener.DidNotReceive().LocalBranchCreated(Args.String); repositoryWatcherListener.Received(1).LocalBranchDeleted("feature/document"); @@ -179,7 +179,7 @@ public async Task ShouldDetectBranchCreate() Logger.Trace("Continue test"); repositoryWatcherListener.DidNotReceive().ConfigChanged(); - repositoryWatcherListener.DidNotReceive().HeadChanged(Args.String); + repositoryWatcherListener.DidNotReceive().HeadChanged(); repositoryWatcherListener.DidNotReceive().IndexChanged(); repositoryWatcherListener.Received(1).LocalBranchCreated("feature/document2"); repositoryWatcherListener.DidNotReceive().LocalBranchDeleted(Args.String); @@ -201,7 +201,7 @@ public async Task ShouldDetectBranchCreate() Logger.Trace("Continue test"); repositoryWatcherListener.DidNotReceive().ConfigChanged(); - repositoryWatcherListener.DidNotReceive().HeadChanged(Args.String); + repositoryWatcherListener.DidNotReceive().HeadChanged(); repositoryWatcherListener.DidNotReceive().IndexChanged(); repositoryWatcherListener.Received(1).LocalBranchCreated("feature2/document2"); repositoryWatcherListener.DidNotReceive().LocalBranchDeleted(Args.String); @@ -250,7 +250,7 @@ public async Task ShouldDetectChangesToRemotes() Logger.Trace("Continue test"); repositoryWatcherListener.Received().ConfigChanged(); - repositoryWatcherListener.DidNotReceive().HeadChanged(Args.String); + repositoryWatcherListener.DidNotReceive().HeadChanged(); repositoryWatcherListener.DidNotReceive().IndexChanged(); repositoryWatcherListener.DidNotReceive().LocalBranchCreated(Args.String); repositoryWatcherListener.DidNotReceive().LocalBranchDeleted(Args.String); @@ -275,7 +275,7 @@ public async Task ShouldDetectChangesToRemotes() Logger.Trace("Continue 2nd test"); repositoryWatcherListener.Received().ConfigChanged(); - repositoryWatcherListener.DidNotReceive().HeadChanged(Args.String); + repositoryWatcherListener.DidNotReceive().HeadChanged(); repositoryWatcherListener.DidNotReceive().IndexChanged(); repositoryWatcherListener.DidNotReceive().LocalBranchCreated(Args.String); repositoryWatcherListener.DidNotReceive().LocalBranchDeleted(Args.String); @@ -321,7 +321,7 @@ public async Task ShouldDetectGitPull() Logger.Trace("Continue test"); repositoryWatcherListener.DidNotReceive().ConfigChanged(); - repositoryWatcherListener.DidNotReceive().HeadChanged(Args.String); + repositoryWatcherListener.DidNotReceive().HeadChanged(); repositoryWatcherListener.Received().IndexChanged(); repositoryWatcherListener.DidNotReceive().LocalBranchCreated(Args.String); repositoryWatcherListener.DidNotReceive().LocalBranchDeleted(Args.String); @@ -366,7 +366,7 @@ public async Task ShouldDetectGitFetch() Logger.Trace("Continue test"); repositoryWatcherListener.DidNotReceive().ConfigChanged(); - repositoryWatcherListener.DidNotReceive().HeadChanged(Args.String); + repositoryWatcherListener.DidNotReceive().HeadChanged(); repositoryWatcherListener.DidNotReceive().IndexChanged(); repositoryWatcherListener.DidNotReceive().LocalBranchCreated(Args.String); repositoryWatcherListener.DidNotReceive().LocalBranchDeleted(Args.String); @@ -394,7 +394,7 @@ private RepositoryWatcher CreateRepositoryWatcher(NPath path) public interface IRepositoryWatcherListener { void ConfigChanged(); - void HeadChanged(string obj); + void HeadChanged(); void IndexChanged(); void LocalBranchCreated(string branch); void LocalBranchDeleted(string branch); @@ -411,10 +411,10 @@ public static void AttachListener(this IRepositoryWatcherListener listener, IRep { var logger = trace ? Logging.GetLogger() : null; - repositoryWatcher.HeadChanged += s => + repositoryWatcher.HeadChanged += () => { - logger?.Trace("HeadChanged: {0}", s); - listener.HeadChanged(s); + logger?.Trace("HeadChanged"); + listener.HeadChanged(); autoResetEvent?.HeadChanged.Set(); }; @@ -453,13 +453,6 @@ public static void AttachListener(this IRepositoryWatcherListener listener, IRep autoResetEvent?.LocalBranchDeleted.Set(); }; - //repositoryWatcher.RemoteBranchChanged += (s, s1) => - //{ - // logger?.Trace("RemoteBranchChanged: {0} {1}", s, s1); - // listener.RemoteBranchChanged(s, s1); - // autoResetEvent?.RemoteBranchChanged.Set(); - //}; - repositoryWatcher.RemoteBranchCreated += (s, s1) => { logger?.Trace("RemoteBranchCreated: {0} {1}", s, s1); @@ -485,7 +478,7 @@ public static void AttachListener(this IRepositoryWatcherListener listener, IRep public static void AssertDidNotReceiveAnyCalls(this IRepositoryWatcherListener repositoryWatcherListener) { repositoryWatcherListener.DidNotReceive().ConfigChanged(); - repositoryWatcherListener.DidNotReceive().HeadChanged(Args.String); + repositoryWatcherListener.DidNotReceive().HeadChanged(); repositoryWatcherListener.DidNotReceive().IndexChanged(); repositoryWatcherListener.DidNotReceive().LocalBranchCreated(Args.String); repositoryWatcherListener.DidNotReceive().LocalBranchDeleted(Args.String); diff --git a/src/tests/IntegrationTests/IntegrationTests.csproj b/src/tests/IntegrationTests/IntegrationTests.csproj index 6d5c2fbdf..36f83c206 100644 --- a/src/tests/IntegrationTests/IntegrationTests.csproj +++ b/src/tests/IntegrationTests/IntegrationTests.csproj @@ -83,8 +83,8 @@ - + diff --git a/src/tests/IntegrationTests/SetupFixture.cs b/src/tests/IntegrationTests/SetUpFixture.cs similarity index 75% rename from src/tests/IntegrationTests/SetupFixture.cs rename to src/tests/IntegrationTests/SetUpFixture.cs index 10ab573b3..21c14f375 100644 --- a/src/tests/IntegrationTests/SetupFixture.cs +++ b/src/tests/IntegrationTests/SetUpFixture.cs @@ -5,7 +5,7 @@ namespace IntegrationTests { [SetUpFixture] - public class SetupFixture + public class SetUpFixture { [SetUp] public void Setup() @@ -13,8 +13,8 @@ public void Setup() Logging.TracingEnabled = true; Logging.LogAdapter = new MultipleLogAdapter( - new FileLogAdapter($"..\\{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}-integration-tests.log"), - new ConsoleLogAdapter() + new FileLogAdapter($"..\\{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}-integration-tests.log") + //, new ConsoleLogAdapter() ); } } diff --git a/src/tests/TestUtils/Events/IRepositoryListener.cs b/src/tests/TestUtils/Events/IRepositoryListener.cs new file mode 100644 index 000000000..5a56ed4c1 --- /dev/null +++ b/src/tests/TestUtils/Events/IRepositoryListener.cs @@ -0,0 +1,120 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using GitHub.Unity; +using NSubstitute; + +namespace TestUtils.Events +{ + interface IRepositoryListener + { + void OnStatusChanged(GitStatus status); + void OnCurrentBranchChanged(string branch); + void OnCurrentRemoteChanged(string remote); + void OnLocalBranchListChanged(); + void OnRemoteBranchListChanged(); + void OnHeadChanged(); + void OnLocksChanged(IEnumerable locks); + void OnRepositoryInfoChanged(); + } + + class RepositoryEvents + { + public EventWaitHandle OnStatusChanged { get; } = new AutoResetEvent(false); + public EventWaitHandle OnCurrentBranchChanged { get; } = new AutoResetEvent(false); + public EventWaitHandle OnCurrentRemoteChanged { get; } = new AutoResetEvent(false); + public EventWaitHandle OnLocalBranchListChanged { get; } = new AutoResetEvent(false); + public EventWaitHandle OnRemoteBranchListChanged { get; } = new AutoResetEvent(false); + public EventWaitHandle OnHeadChanged { get; } = new AutoResetEvent(false); + public EventWaitHandle OnLocksChanged { get; } = new AutoResetEvent(false); + public EventWaitHandle OnRepositoryInfoChanged { get; } = new AutoResetEvent(false); + + public void Reset() + { + OnStatusChanged.Reset(); + OnCurrentBranchChanged.Reset(); + OnCurrentRemoteChanged.Reset(); + OnLocalBranchListChanged.Reset(); + OnRemoteBranchListChanged.Reset(); + OnHeadChanged.Reset(); + OnLocksChanged.Reset(); + OnRepositoryInfoChanged.Reset(); + } + } + + static class RepositoryListenerExtensions + { + public static void AttachListener(this IRepositoryListener listener, + IRepository repository, RepositoryEvents repositoryEvents = null, bool trace = true) + { + var logger = trace ? Logging.GetLogger() : null; + + repository.OnStatusChanged += gitStatus => + { + logger?.Trace("OnStatusChanged: {0}", gitStatus); + listener.OnStatusChanged(gitStatus); + repositoryEvents?.OnStatusChanged.Set(); + }; + + repository.OnCurrentBranchChanged += name => + { + logger?.Debug("OnCurrentBranchChanged: {0}", name); + listener.OnCurrentBranchChanged(name); + repositoryEvents?.OnCurrentBranchChanged.Set(); + }; + + repository.OnCurrentRemoteChanged += name => + { + logger?.Debug("OnCurrentRemoteChanged: {0}", name); + listener.OnCurrentRemoteChanged(name); + repositoryEvents?.OnCurrentRemoteChanged.Set(); + }; + + repository.OnLocalBranchListChanged += () => + { + logger?.Debug("OnLocalBranchListChanged"); + listener.OnLocalBranchListChanged(); + repositoryEvents?.OnLocalBranchListChanged.Set(); + }; + + repository.OnRemoteBranchListChanged += () => + { + logger?.Debug("OnRemoteBranchListChanged"); + listener.OnRemoteBranchListChanged(); + repositoryEvents?.OnRemoteBranchListChanged.Set(); + }; + + repository.OnCurrentBranchUpdated += () => + { + logger?.Debug("OnHeadChanged"); + listener.OnHeadChanged(); + repositoryEvents?.OnHeadChanged.Set(); + }; + + repository.OnLocksChanged += locks => + { + logger?.Debug("OnLocksChanged: {0}", locks); + listener.OnLocksChanged(locks); + repositoryEvents?.OnLocksChanged.Set(); + }; + + repository.OnRepositoryInfoChanged += () => + { + logger?.Debug("OnRepositoryInfoChanged"); + listener.OnRepositoryInfoChanged(); + repositoryEvents?.OnRepositoryInfoChanged.Set(); + }; + } + + public static void AssertDidNotReceiveAnyCalls(this IRepositoryListener repositoryListener) + { + repositoryListener.DidNotReceive().OnStatusChanged(Args.GitStatus); + repositoryListener.DidNotReceive().OnCurrentBranchChanged(Args.String); + repositoryListener.DidNotReceive().OnCurrentRemoteChanged(Args.String); + repositoryListener.DidNotReceive().OnLocalBranchListChanged(); + repositoryListener.DidNotReceive().OnHeadChanged(); + repositoryListener.DidNotReceive().OnLocksChanged(Arg.Any>()); + repositoryListener.DidNotReceive().OnRepositoryInfoChanged(); + } + } +}; \ No newline at end of file diff --git a/src/tests/TestUtils/Events/IRepositoryManagerListener.cs b/src/tests/TestUtils/Events/IRepositoryManagerListener.cs index d9c984e6c..1f7432652 100644 --- a/src/tests/TestUtils/Events/IRepositoryManagerListener.cs +++ b/src/tests/TestUtils/Events/IRepositoryManagerListener.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -8,50 +9,86 @@ namespace TestUtils.Events { interface IRepositoryManagerListener { - void OnStatusUpdate(GitStatus status); - void OnActiveBranchChanged(ConfigBranch? branch); - void OnActiveRemoteChanged(ConfigRemote? remote); - void OnLocalBranchListChanged(); - void OnRemoteBranchListChanged(); void OnIsBusyChanged(bool busy); + void OnStatusUpdated(GitStatus status); void OnLocksUpdated(IEnumerable locks); + void OnLocalBranchListUpdated(Dictionary branchList); + void OnRemoteBranchListUpdated(Dictionary remotesList, Dictionary> remoteBranchList); + void OnLocalBranchUpdated(string name); + void OnLocalBranchAdded(string name); + void OnLocalBranchRemoved(string name); + void OnRemoteBranchAdded(string origin, string name); + void OnRemoteBranchRemoved(string origin, string name); + void OnGitUserLoaded(IUser user); + void OnCurrentBranchUpdated(ConfigBranch? configBranch); + void OnCurrentRemoteUpdated(ConfigRemote? configRemote); } class RepositoryManagerEvents { - public EventWaitHandle OnIsBusy { get; } = new ManualResetEvent(false); - public EventWaitHandle OnIsNotBusy { get; } = new ManualResetEvent(false); - public AutoResetEvent OnStatusUpdate { get; } = new AutoResetEvent(false); - public EventWaitHandle OnActiveBranchChanged { get; } = new ManualResetEvent(false); - public EventWaitHandle OnActiveRemoteChanged { get; } = new ManualResetEvent(false); - public EventWaitHandle OnLocalBranchListChanged { get; } = new ManualResetEvent(false); - public EventWaitHandle OnRemoteBranchListChanged { get; } = new ManualResetEvent(false); - public EventWaitHandle OnLocksUpdated { get; } = new ManualResetEvent(false); + public EventWaitHandle OnIsBusy { get; } = new AutoResetEvent(false); + public EventWaitHandle OnIsNotBusy { get; } = new AutoResetEvent(false); + public EventWaitHandle OnStatusUpdated { get; } = new AutoResetEvent(false); + public EventWaitHandle OnLocksUpdated { get; } = new AutoResetEvent(false); + public EventWaitHandle OnCurrentBranchUpdated { get; } = new AutoResetEvent(false); + public EventWaitHandle OnCurrentRemoteUpdated { get; } = new AutoResetEvent(false); + public EventWaitHandle OnHeadUpdated { get; } = new AutoResetEvent(false); + public EventWaitHandle OnLocalBranchListUpdated { get; } = new AutoResetEvent(false); + public EventWaitHandle OnRemoteBranchListUpdated { get; } = new AutoResetEvent(false); + public EventWaitHandle OnLocalBranchUpdated { get; } = new AutoResetEvent(false); + public EventWaitHandle OnLocalBranchAdded { get; } = new AutoResetEvent(false); + public EventWaitHandle OnLocalBranchRemoved { get; } = new AutoResetEvent(false); + public EventWaitHandle OnRemoteBranchAdded { get; } = new AutoResetEvent(false); + public EventWaitHandle OnRemoteBranchRemoved { get; } = new AutoResetEvent(false); + public EventWaitHandle OnGitUserLoaded { get; } = new AutoResetEvent(false); public void Reset() { OnIsBusy.Reset(); OnIsNotBusy.Reset(); - OnStatusUpdate.Reset(); - OnActiveBranchChanged.Reset(); - OnActiveRemoteChanged.Reset(); - OnLocalBranchListChanged.Reset(); - OnRemoteBranchListChanged.Reset(); + OnStatusUpdated.Reset(); OnLocksUpdated.Reset(); + OnCurrentBranchUpdated.Reset(); + OnCurrentRemoteUpdated.Reset(); + OnHeadUpdated.Reset(); + OnLocalBranchListUpdated.Reset(); + OnRemoteBranchListUpdated.Reset(); + OnLocalBranchUpdated.Reset(); + OnLocalBranchAdded.Reset(); + OnLocalBranchRemoved.Reset(); + OnRemoteBranchAdded.Reset(); + OnRemoteBranchRemoved.Reset(); + OnGitUserLoaded.Reset(); + } + + public void WaitForNotBusy(int seconds = 1) + { + OnIsBusy.WaitOne(TimeSpan.FromSeconds(seconds)); + OnIsNotBusy.WaitOne(TimeSpan.FromSeconds(seconds)); + } + + public void WaitForStatusUpdated(int seconds = 1) + { + OnStatusUpdated.WaitOne(TimeSpan.FromSeconds(seconds)); + } + + public void WaitForHeadUpdated(int seconds = 1) + { + OnHeadUpdated.WaitOne(TimeSpan.FromSeconds(seconds)); } } static class RepositoryManagerListenerExtensions { - public static void AttachListener(this IRepositoryManagerListener listener, IRepositoryManager repositoryManager, - RepositoryManagerEvents managerEvents = null, bool trace = true) + public static void AttachListener(this IRepositoryManagerListener listener, + IRepositoryManager repositoryManager, RepositoryManagerEvents managerEvents = null, bool trace = true) { var logger = trace ? Logging.GetLogger() : null; - repositoryManager.OnIsBusyChanged += b => { - logger?.Trace("OnIsBusyChanged: {0}", b); - listener.OnIsBusyChanged(b); - if (b) + repositoryManager.OnIsBusyChanged += isBusy => { + logger?.Trace("OnIsBusyChanged: {0}", isBusy); + listener.OnIsBusyChanged(isBusy); + if (isBusy) managerEvents?.OnIsBusy.Set(); else managerEvents?.OnIsNotBusy.Set(); @@ -59,50 +96,92 @@ public static void AttachListener(this IRepositoryManagerListener listener, IRep repositoryManager.OnStatusUpdated += status => { logger?.Debug("OnStatusUpdated: {0}", status); - listener.OnStatusUpdate(status); - managerEvents?.OnStatusUpdate.Set(); + listener.OnStatusUpdated(status); + managerEvents?.OnStatusUpdated.Set(); }; - repositoryManager.OnActiveBranchChanged += (branch) => { - logger?.Trace($"OnActiveBranchChanged {branch}"); - listener.OnActiveBranchChanged(branch); - managerEvents?.OnActiveBranchChanged.Set(); + repositoryManager.OnLocksUpdated += locks => { + var lockArray = locks.ToArray(); + logger?.Trace("OnLocksUpdated Count:{0}", lockArray.Length); + listener.OnLocksUpdated(lockArray); + managerEvents?.OnLocksUpdated.Set(); }; - repositoryManager.OnActiveRemoteChanged += (remote) => { - logger?.Trace($"OnActiveRemoteChanged {(remote.HasValue ? remote.Value.Name : null)}"); - listener.OnActiveRemoteChanged(remote); - managerEvents?.OnActiveRemoteChanged.Set(); + repositoryManager.OnCurrentBranchUpdated += configBranch => { + logger?.Trace("OnCurrentBranchUpdated"); + listener.OnCurrentBranchUpdated(configBranch); + managerEvents?.OnCurrentBranchUpdated.Set(); }; - repositoryManager.OnLocalBranchListChanged += () => { - logger?.Trace("OnLocalBranchListChanged"); - listener.OnLocalBranchListChanged(); - managerEvents?.OnLocalBranchListChanged.Set(); + repositoryManager.OnCurrentRemoteUpdated += configRemote => { + logger?.Trace("OnCurrentRemoteUpdated"); + listener.OnCurrentRemoteUpdated(configRemote); + managerEvents?.OnCurrentRemoteUpdated.Set(); }; - repositoryManager.OnRemoteBranchListChanged += () => { - logger?.Trace("OnRemoteBranchListChanged"); - listener.OnRemoteBranchListChanged(); - managerEvents?.OnRemoteBranchListChanged.Set(); + repositoryManager.OnLocalBranchListUpdated += branchList => { + logger?.Trace("OnLocalBranchListUpdated"); + listener.OnLocalBranchListUpdated(branchList); + managerEvents?.OnLocalBranchListUpdated.Set(); }; - repositoryManager.OnLocksUpdated += locks => { - var lockArray = locks.ToArray(); - logger?.Trace("OnLocksUpdated Count:{0}", lockArray.Length); - listener.OnLocksUpdated(lockArray); - managerEvents?.OnLocksUpdated.Set(); + repositoryManager.OnRemoteBranchListUpdated += (remotesList, branchList) => { + logger?.Trace("OnRemoteBranchListUpdated"); + listener.OnRemoteBranchListUpdated(remotesList, branchList); + managerEvents?.OnRemoteBranchListUpdated.Set(); + }; + + repositoryManager.OnLocalBranchUpdated += name => { + logger?.Trace("OnLocalBranchUpdated Name:{0}", name); + listener.OnLocalBranchUpdated(name); + managerEvents?.OnLocalBranchUpdated.Set(); + }; + + repositoryManager.OnLocalBranchAdded += name => { + logger?.Trace("OnLocalBranchAdded Name:{0}", name); + listener.OnLocalBranchAdded(name); + managerEvents?.OnLocalBranchAdded.Set(); + }; + + repositoryManager.OnLocalBranchRemoved += name => { + logger?.Trace("OnLocalBranchRemoved Name:{0}", name); + listener.OnLocalBranchRemoved(name); + managerEvents?.OnLocalBranchRemoved.Set(); + }; + + repositoryManager.OnRemoteBranchAdded += (origin, name) => { + logger?.Trace("OnRemoteBranchAdded Origin:{0} Name:{1}", origin, name); + listener.OnRemoteBranchAdded(origin, name); + managerEvents?.OnRemoteBranchAdded.Set(); + }; + + repositoryManager.OnRemoteBranchRemoved += (origin, name) => { + logger?.Trace("OnRemoteBranchRemoved Origin:{0} Name:{1}", origin, name); + listener.OnRemoteBranchRemoved(origin, name); + managerEvents?.OnRemoteBranchRemoved.Set(); + }; + + repositoryManager.OnGitUserLoaded += user => { + logger?.Trace("OnGitUserLoaded Name:{0}", user); + listener.OnGitUserLoaded(user); + managerEvents?.OnGitUserLoaded.Set(); }; } public static void AssertDidNotReceiveAnyCalls(this IRepositoryManagerListener repositoryManagerListener) { - repositoryManagerListener.DidNotReceive().OnStatusUpdate(Args.GitStatus); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); + repositoryManagerListener.DidNotReceive().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.DidNotReceive().OnStatusUpdated(Args.GitStatus); repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); + repositoryManagerListener.DidNotReceive().OnCurrentBranchUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnCurrentRemoteUpdated(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnLocalBranchListUpdated(Arg.Any>()); + repositoryManagerListener.DidNotReceive().OnRemoteBranchListUpdated(Arg.Any>(), Arg.Any>>()); + repositoryManagerListener.DidNotReceive().OnLocalBranchUpdated(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchAdded(Args.String); + repositoryManagerListener.DidNotReceive().OnLocalBranchRemoved(Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchAdded(Args.String, Args.String); + repositoryManagerListener.DidNotReceive().OnRemoteBranchRemoved(Args.String, Args.String); } } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/tests/TestUtils/Helpers/Args.cs b/src/tests/TestUtils/Helpers/Args.cs index c9bee3617..650d65e73 100644 --- a/src/tests/TestUtils/Helpers/Args.cs +++ b/src/tests/TestUtils/Helpers/Args.cs @@ -17,6 +17,7 @@ static class Args public static GitConfigSource GitConfigSource { get { return Arg.Any(); } } public static GitStatus GitStatus { get { return Arg.Any(); } } public static IEnumerable EnumerableGitLock { get { return Arg.Any>(); } } + public static IUser User { get { return Arg.Any(); } } public static ITask GitStatusTask { diff --git a/src/tests/TestUtils/TestUtils.csproj b/src/tests/TestUtils/TestUtils.csproj index d0f7c53f4..e2072b12b 100644 --- a/src/tests/TestUtils/TestUtils.csproj +++ b/src/tests/TestUtils/TestUtils.csproj @@ -58,6 +58,7 @@ + diff --git a/src/tests/UnitTests/Git/RepositoryTests.cs b/src/tests/UnitTests/Git/RepositoryTests.cs new file mode 100644 index 000000000..368d4a86b --- /dev/null +++ b/src/tests/UnitTests/Git/RepositoryTests.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using FluentAssertions; +using GitHub.Unity; +using NCrunch.Framework; +using NSubstitute; +using NUnit.Framework; +using TestUtils; +using TestUtils.Events; + +namespace UnitTests +{ + [TestFixture, Isolated] + public class RepositoryTests + { + private static readonly SubstituteFactory SubstituteFactory = new SubstituteFactory(); + + private static Repository LoadRepository() + { + var fileSystem = SubstituteFactory.CreateFileSystem( + new CreateFileSystemOptions + { + + }); + + NPath.FileSystem = fileSystem; + + return new Repository("TestRepo", @"C:\Repo".ToNPath()); + } + + private RepositoryEvents repositoryEvents; + private TimeSpan repositoryEventsTimeout; + + [SetUp] + public void OnSetup() + { + repositoryEvents = new RepositoryEvents(); + repositoryEventsTimeout = TimeSpan.FromSeconds(0.5); + } + + [Test] + public void Repository() + { + var repository = LoadRepository(); + var repositoryManager = Substitute.For(); + + var repositoryListener = Substitute.For(); + repositoryListener.AttachListener(repository, repositoryEvents); + + var origin = new ConfigRemote + { + Name = "origin", + Url = "https://github.com/someUser/someRepo.git" + }; + + var remotes = new[] { origin }; + + var remoteDictionary = remotes.ToDictionary(remote => remote.Name); + + var masterOriginBranch = new ConfigBranch { Name = "master", Remote = origin }; + + var branches = new[] { + masterOriginBranch, + new ConfigBranch { Name = "features/feature-1", Remote = origin } + }; + + var branchDictionary = branches.ToDictionary(branch => branch.Name); + + var remoteBranches = new[] { + new ConfigBranch { Name = "master", Remote = origin }, + new ConfigBranch { Name = "features/feature-1", Remote = origin }, + new ConfigBranch { Name = "features/feature-2", Remote = origin } + }; + + var remoteBranchDictionary = remoteBranches + .GroupBy(branch => branch.Remote.Value.Name) + .ToDictionary(grouping => grouping.Key, + grouping => grouping.ToDictionary(branch => branch.Name)); + + repository.Initialize(repositoryManager); + + string expectedBranch = null; + repository.OnCurrentBranchChanged += branch => { + expectedBranch = branch; + }; + + string expectedRemote = null; + repository.OnCurrentRemoteChanged += remote => { + expectedRemote = remote; + }; + + repositoryManager.OnLocalBranchListUpdated += Raise.Event>>(branchDictionary); + + repositoryEvents.OnLocalBranchListChanged.WaitOne(repositoryEventsTimeout).Should().BeTrue("OnLocalBranchListChanged not raised"); + + repositoryManager.OnRemoteBranchListUpdated += Raise.Event, Dictionary>>>(remoteDictionary, remoteBranchDictionary); + + repositoryEvents.OnRemoteBranchListChanged.WaitOne(repositoryEventsTimeout).Should().BeTrue("OnRemoteBranchListChanged not raised"); + + repositoryManager.OnCurrentBranchUpdated += Raise.Event>(masterOriginBranch); + repositoryManager.OnCurrentRemoteUpdated += Raise.Event>(origin); + + repositoryEvents.OnCurrentBranchChanged.WaitOne(repositoryEventsTimeout).Should().BeTrue("OnCurrentBranchChanged not raised"); + repositoryEvents.OnCurrentRemoteChanged.WaitOne(repositoryEventsTimeout).Should().BeTrue("OnCurrentRemoteChanged not raised"); + repositoryEvents.OnRepositoryInfoChanged.WaitOne(repositoryEventsTimeout).Should().BeTrue("OnRepositoryInfoChanged not raised"); + + expectedBranch.Should().Be("master"); + expectedRemote.Should().Be("origin"); + } + } +} diff --git a/src/tests/UnitTests/Repository/RepositoryManagerTests.cs b/src/tests/UnitTests/Repository/RepositoryManagerTests.cs deleted file mode 100644 index be6bfbbe6..000000000 --- a/src/tests/UnitTests/Repository/RepositoryManagerTests.cs +++ /dev/null @@ -1,325 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Threading; -using FluentAssertions; -using GitHub.Unity; -using NSubstitute; -using NUnit.Framework; -using TestUtils; -using TestUtils.Events; -using System.Threading.Tasks; - -namespace UnitTests -{ - [TestFixture(Ignore = true, IgnoreReason = "Disabling temporarily until we mock TaskRunner properly")] - class RepositoryManagerTests - { - [SetUp] - public void SetUp() - { - NPath.FileSystem = - SubstituteFactory.CreateFileSystem(new CreateFileSystemOptions() { - ChildFiles = - new Dictionary> { - { - new SubstituteFactory.ContentsKey(@"c:\Temp\.git\refs\heads", "*", - SearchOption.TopDirectoryOnly), - new[] { "master" } - }, { - new SubstituteFactory.ContentsKey(@"c:\Temp\.git\refs\heads\features", "*", - SearchOption.TopDirectoryOnly), - new[] { "feature1" } - }, - }, - ChildDirectories = - new Dictionary> { - { - new SubstituteFactory.ContentsKey(@"c:\Temp\.git\refs\heads", "*", - SearchOption.TopDirectoryOnly), - new[] { @"c:\Temp\.git\refs\heads\features" } - }, { - new SubstituteFactory.ContentsKey(@"c:\Temp\.git\refs\heads\features", "*", - SearchOption.TopDirectoryOnly), - new string[0] - }, - }, - FileContents = - new Dictionary> { - { @"c:\Temp\.git\HEAD", new[] { "ref: refs/heads/fixes/repository-manager-refresh" } } - } - }); - - platform = SubstituteFactory.CreatePlatform(); - SynchronizationContext.SetSynchronizationContext(new TestSynchronizationContext()); - taskManager = new TaskManager(TaskScheduler.FromCurrentSynchronizationContext()); - repositoryPathConfiguration = new RepositoryPathConfiguration(@"/Temp".ToNPath()); - gitConfig = SubstituteFactory.CreateGitConfig(); - - repositoryWatcher = SubstituteFactory.CreateRepositoryWatcher(); - - gitConfigGetResults = new Dictionary { - { - new CreateRepositoryProcessRunnerOptions.GitConfigGetKey("user.email", GitConfigSource.User), - "someone@somewhere.com" - }, { - new CreateRepositoryProcessRunnerOptions.GitConfigGetKey("user.name", GitConfigSource.User), - "Someone Somewhere" - } - }; - } - - [TestFixtureSetUp] - public void TestFixtureSetUp() - { - SubstituteFactory = new SubstituteFactory(); - } - - private readonly CancellationToken cancellationToken = CancellationToken.None; - private IRepositoryWatcher repositoryWatcher; - private IPlatform platform; - private ITaskManager taskManager; - private RepositoryPathConfiguration repositoryPathConfiguration; - private IGitConfig gitConfig; - private Dictionary gitConfigGetResults; - - protected SubstituteFactory SubstituteFactory { get; private set; } - - private RepositoryManager CreateRepositoryManager(IGitClient gitClient) - { - return new RepositoryManager(platform, taskManager, new NullUsageTracker(), gitConfig, repositoryWatcher, - gitClient, repositoryPathConfiguration, cancellationToken); - } - - private IGitClient CreateRepositoryProcessRunner(GitStatus? gitStatusResults = null, - List gitListLocksResults = null) - { - return - SubstituteFactory.CreateRepositoryProcessRunner(new CreateRepositoryProcessRunnerOptions { - GitConfigGetResults = gitConfigGetResults, - GitStatusResults = gitStatusResults, - GitListLocksResults = gitListLocksResults - }); - } - - [Test] - public void ShouldBeConstructable() - { - var repositoryProcessRunner = CreateRepositoryProcessRunner(); - var repositoryManager = CreateRepositoryManager(repositoryProcessRunner); - repositoryManager.Should().NotBeNull(); - } - - [Test] - public void ShouldNotRefreshIfNoGitStatusIsReturned() - { - var repositoryProcessRunner = CreateRepositoryProcessRunner(null, new List()); - var repositoryManager = CreateRepositoryManager(repositoryProcessRunner); - repositoryManager.Initialize(); - repositoryManager.Start(); - - var repositoryManagerListener = Substitute.For(); - repositoryManagerListener.AttachListener(repositoryManager); - - repositoryManager.Refresh(); - - Thread.Sleep(1000); - - repositoryManagerListener.AssertDidNotReceiveAnyCalls(); - } - - [Test] - public async Task ShouldRefreshAndReturnCombinedStatusAndLockInformation1() - { - var responseGitStatus = new GitStatus { - LocalBranch = "master", - Entries = - new List { - new GitStatusEntry("SomeLockedBinary.psd", null, "SomeLockedBinary.psd", GitFileStatus.Modified), - new GitStatusEntry("subFolder/AnotherLockedBinary.psd", null, "subFolder/AnotherLockedBinary.psd", GitFileStatus.Modified), - new GitStatusEntry("subFolder/UnLockedBinary.psd", null, "subFolder/UnLockedBinary.psd", GitFileStatus.Modified), - } - }; - - var responseGitLocks = new List { - new GitLock("SomeLockedBinary.psd", "SomeLockedBinary.psd", "Someone", 1), - new GitLock("SomeoneElsesBinary.psd", "SomeoneElsesBinary.psd", "SomeoneElse", 2), - new GitLock("subFolder/AnotherLockedBinary.psd", "subFolder/AnotherLockedBinary.psd", "Someone", 3), - }; - - var expectedGitStatus = new GitStatus { - LocalBranch = "master", - Entries = - new List { - new GitStatusEntry("SomeLockedBinary.psd", null, "SomeLockedBinary.psd", - GitFileStatus.Modified), - new GitStatusEntry("subFolder/AnotherLockedBinary.psd", null, "subFolder/AnotherLockedBinary.psd", - GitFileStatus.Modified), - new GitStatusEntry("subFolder/UnLockedBinary.psd", null, "subFolder/UnLockedBinary.psd", GitFileStatus.Modified), - } - }; - - var repositoryProcessRunner = CreateRepositoryProcessRunner(responseGitStatus, responseGitLocks); - - var repositoryManager = CreateRepositoryManager(repositoryProcessRunner); - repositoryManager.Initialize(); - repositoryManager.Start(); - - var repositoryManagerListener = Substitute.For(); - repositoryManagerListener.AttachListener(repositoryManager); - - GitStatus? result = null; - repositoryManager.OnStatusUpdated += s => { result = s; }; - - repositoryManager.Refresh(); - - await TaskEx.Delay(1000); - - repositoryManagerListener.Received(1).OnStatusUpdate(Args.GitStatus); - - result.HasValue.Should().BeTrue(); - result.Value.AssertEqual(expectedGitStatus); - - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); - } - - [Test] - public void ShouldRefreshAndReturnCombinedStatusAndLockInformation2() - { - var responseGitStatus = new GitStatus { - LocalBranch = "master", - Entries = - new List { - new GitStatusEntry("SomeLockedBinary.psd", null, "SomeLockedBinary.psd", GitFileStatus.Modified), - new GitStatusEntry("subFolder/AnotherLockedBinary.psd", null, "subFolder/AnotherLockedBinary.psd", GitFileStatus.Modified), - new GitStatusEntry("subFolder/UnLockedBinary.psd", null, "subFolder/UnLockedBinary.psd", GitFileStatus.Modified), - } - }; - - var responseGitLocks = new List { - new GitLock("SomeLockedBinary.psd", "SomeLockedBinary.psd", "Someone", 1), - new GitLock("SomeoneElsesBinary.psd", "SomeoneElsesBinary.psd", "SomeoneElse", 2), - new GitLock("subFolder/AnotherLockedBinary.psd", "subFolder/AnotherLockedBinary.psd", "Someone", 3), - }; - - var expectedGitStatus = new GitStatus { - LocalBranch = "master", - Entries = - new List { - new GitStatusEntry("SomeLockedBinary.psd", null, "SomeLockedBinary.psd", GitFileStatus.Modified), - new GitStatusEntry("subFolder/AnotherLockedBinary.psd", null, "subFolder/AnotherLockedBinary.psd", GitFileStatus.Modified), - new GitStatusEntry("subFolder/UnLockedBinary.psd", null, "subFolder/UnLockedBinary.psd", GitFileStatus.Modified - - //This lock intentionally left missing to catch false positives - //,gitLock: new GitLock("subFolder/AnotherLockedBinary.psd", "subFolder/AnotherLockedBinary.psd", "Someone") - - ), - } - }; - - var repositoryProcessRunner = CreateRepositoryProcessRunner(responseGitStatus, responseGitLocks); - - var repositoryManager = CreateRepositoryManager(repositoryProcessRunner); - repositoryManager.Initialize(); - repositoryManager.Start(); - - var repositoryManagerListener = Substitute.For(); - repositoryManagerListener.AttachListener(repositoryManager); - - GitStatus? result = null; - repositoryManager.OnStatusUpdated += s => { result = s; }; - - repositoryManager.Refresh(); - - Thread.Sleep(1000); - - repositoryManagerListener.Received(1).OnStatusUpdate(Args.GitStatus); - - result.HasValue.Should().BeTrue(); - result.Value.AssertNotEqual(expectedGitStatus); - - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); - } - - [Test] - public void ShouldRefreshAndReturnWithEmptyGitLockResponse() - { - var responseGitStatus = new GitStatus { - LocalBranch = "master", - Entries = - new List { - new GitStatusEntry("Some.sln", null, "Some.sln", GitFileStatus.Modified) - } - }; - - var repositoryProcessRunner = CreateRepositoryProcessRunner(responseGitStatus, new List()); - - var repositoryManager = CreateRepositoryManager(repositoryProcessRunner); - repositoryManager.Initialize(); - repositoryManager.Start(); - - var repositoryManagerListener = Substitute.For(); - repositoryManagerListener.AttachListener(repositoryManager); - - GitStatus? result = null; - repositoryManager.OnStatusUpdated += s => { result = s; }; - - repositoryManager.Refresh(); - - Thread.Sleep(1000); - - repositoryManagerListener.Received(1).OnStatusUpdate(Args.GitStatus); - - result.HasValue.Should().BeTrue(); - result.Value.AssertEqual(responseGitStatus); - - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); - } - - [Test] - public void ShouldRefreshAndReturnWithNoGitLockResponse() - { - var responseGitStatus = new GitStatus { - LocalBranch = "master", - Entries = - new List { - new GitStatusEntry("Some.sln", null, "Some.sln", GitFileStatus.Modified) - } - }; - - var repositoryProcessRunner = CreateRepositoryProcessRunner(responseGitStatus, new List()); - - var repositoryManager = CreateRepositoryManager(repositoryProcessRunner); - repositoryManager.Initialize(); - repositoryManager.Start(); - - var repositoryManagerListener = Substitute.For(); - repositoryManagerListener.AttachListener(repositoryManager); - - GitStatus? result = null; - repositoryManager.OnStatusUpdated += s => { result = s; }; - - repositoryManager.Refresh(); - - Thread.Sleep(1000); - - repositoryManagerListener.Received(1).OnStatusUpdate(Args.GitStatus); - - result.HasValue.Should().BeTrue(); - result.Value.AssertEqual(responseGitStatus); - - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); - repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); - repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); - } - } -} diff --git a/src/tests/UnitTests/SetUpFixture.cs b/src/tests/UnitTests/SetUpFixture.cs index 61032b102..8b683ff0e 100644 --- a/src/tests/UnitTests/SetUpFixture.cs +++ b/src/tests/UnitTests/SetUpFixture.cs @@ -12,7 +12,10 @@ public void SetUp() { Logging.TracingEnabled = true; - Logging.LogAdapter = new MultipleLogAdapter(new FileLogAdapter($"..\\{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}-unit-tests.log"), new ConsoleLogAdapter()); + Logging.LogAdapter = new MultipleLogAdapter( + new FileLogAdapter($"..\\{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}-unit-tests.log") + //, new ConsoleLogAdapter() + ); } } } \ No newline at end of file diff --git a/src/tests/UnitTests/UnitTests.csproj b/src/tests/UnitTests/UnitTests.csproj index 2be5fd1db..bf298220e 100644 --- a/src/tests/UnitTests/UnitTests.csproj +++ b/src/tests/UnitTests/UnitTests.csproj @@ -68,16 +68,11 @@ - - $(UnityDir)UnityEditor.dll - - - $(UnityDir)UnityEngine.dll - + @@ -100,7 +95,6 @@ -