diff --git a/src/GitHub.Api/Application/ApplicationManagerBase.cs b/src/GitHub.Api/Application/ApplicationManagerBase.cs index 1f0c79be7..723e33c1d 100644 --- a/src/GitHub.Api/Application/ApplicationManagerBase.cs +++ b/src/GitHub.Api/Application/ApplicationManagerBase.cs @@ -39,17 +39,18 @@ protected void Initialize() Logging.TracingEnabled = UserSettings.Get(Constants.TraceLoggingKey, false); ProcessManager = new ProcessManager(Environment, Platform.GitEnvironment, CancellationToken); Platform.Initialize(ProcessManager, TaskManager); + if (Environment.GitExecutablePath != null) + { + GitClient = new GitClient(Environment, ProcessManager, Platform.CredentialManager, TaskManager); + } + SetupMetrics(); } public virtual async Task Run(bool firstRun) { Logger.Trace("Run - CurrentDirectory {0}", NPath.CurrentDirectory); - if (Environment.GitExecutablePath != null) - { - GitClient = new GitClient(Environment, ProcessManager, Platform.CredentialManager, TaskManager); - } - else + if (Environment.GitExecutablePath == null) { GitClient = new GitClient(Environment, ProcessManager, Platform.CredentialManager, TaskManager); Environment.GitExecutablePath = await DetermineGitExecutablePath(); @@ -69,9 +70,7 @@ public virtual async Task Run(bool firstRun) RestartRepository(); InitializeUI(); - new ActionTask(CancellationToken, SetupMetrics).Start(); new ActionTask(new Task(() => LoadKeychain().Start())).Start(); - new ActionTask(CancellationToken, RunRepositoryManager).Start(); } public ITask InitializeRepository() @@ -104,8 +103,7 @@ public ITask InitializeRepository() .Then(GitClient.Add(filesForInitialCommit)) .Then(GitClient.Commit("Initial commit", null)) .Then(RestartRepository) - .ThenInUI(InitializeUI) - .Then(RunRepositoryManager); + .ThenInUI(InitializeUI); return task; } @@ -114,29 +112,15 @@ public void RestartRepository() Environment.InitializeRepository(); if (Environment.RepositoryPath != null) { - var repositoryPathConfiguration = new RepositoryPathConfiguration(Environment.RepositoryPath); - var gitConfig = new GitConfig(repositoryPathConfiguration.DotGitConfig); - - var repositoryWatcher = new RepositoryWatcher(Platform, repositoryPathConfiguration, TaskManager.Token); - repositoryManager = new RepositoryManager(Platform, TaskManager, UsageTracker, gitConfig, repositoryWatcher, - GitClient, repositoryPathConfiguration, TaskManager.Token); - Environment.Repository = repositoryManager.Repository; + repositoryManager = Unity.RepositoryManager.CreateInstance(Platform, TaskManager, UsageTracker, GitClient, Environment.RepositoryPath); + Environment.Repository = new Repository(GitClient, repositoryManager, Environment.RepositoryPath); + repositoryManager.Initialize(); + Environment.Repository.Initialize(); + repositoryManager.Start(); Logger.Trace($"Got a repository? {Environment.Repository}"); } } - private void RunRepositoryManager() - { - Logger.Trace("RunRepositoryManager"); - - if (Environment.RepositoryPath != null) - { - new ActionTask(repositoryManager.Initialize()) - .Then(repositoryManager.Start) - .Start();; - } - } - private async Task LoadKeychain() { Logger.Trace("Loading Keychain"); diff --git a/src/GitHub.Api/Events/RepositoryWatcher.cs b/src/GitHub.Api/Events/RepositoryWatcher.cs index bf55010bb..a004502d1 100644 --- a/src/GitHub.Api/Events/RepositoryWatcher.cs +++ b/src/GitHub.Api/Events/RepositoryWatcher.cs @@ -21,6 +21,7 @@ interface IRepositoryWatcher : IDisposable event Action RemoteBranchCreated; event Action RemoteBranchDeleted; void Initialize(); + int CheckAndProcessEvents(); } class RepositoryWatcher : IRepositoryWatcher @@ -33,6 +34,9 @@ class RepositoryWatcher : IRepositoryWatcher private NativeInterface nativeInterface; private bool running; private Task task; + private int lastCountOfProcessedEvents = 0; + private bool processingEvents; + private readonly ManualResetEventSlim signalProcessingEventsDone = new ManualResetEventSlim(false); public event Action HeadChanged; public event Action IndexChanged; @@ -112,66 +116,85 @@ private void WatcherLoop() break; } - var fileEvents = nativeInterface.GetEvents(); + CheckAndProcessEvents(); - if (fileEvents.Any()) + if (pauseEvent.Wait(1000)) { - Logger.Trace("Processing {0} Events", fileEvents.Length); + break; } + } + } - var repositoryChanged = false; + public int CheckAndProcessEvents() + { + if (processingEvents) + { + signalProcessingEventsDone.Wait(cancellationToken); + return lastCountOfProcessedEvents; + } - foreach (var fileEvent in fileEvents) - { - if (!running) - { - break; - } + signalProcessingEventsDone.Reset(); + processingEvents = true; + lastCountOfProcessedEvents = 0; + var fileEvents = nativeInterface.GetEvents(); - if (cancellationToken.IsCancellationRequested) - { - Stop(); - break; - } + if (fileEvents.Length > 0) + { + Logger.Trace("Processing {0} Events", fileEvents.Length); + } - //Logger.Trace(fileEvent.Describe()); + var repositoryChanged = false; - var eventDirectory = new NPath(fileEvent.Directory); - var fileA = eventDirectory.Combine(fileEvent.FileA); + foreach (var fileEvent in fileEvents) + { + if (!running) + { + break; + } - NPath fileB = null; - if (fileEvent.FileB != null) - { - fileB = eventDirectory.Combine(fileEvent.FileB); - } + if (cancellationToken.IsCancellationRequested) + { + Stop(); + break; + } - // handling events in .git/* - if (fileA.IsChildOf(paths.DotGitPath)) - { - HandleEventInDotGit(fileEvent, fileA, fileB); - } - else - { - if (repositoryChanged || ignoredPaths.Any(ignoredPath => fileA.IsChildOf(ignoredPath))) - { - continue; - } + //Logger.Trace(fileEvent.Describe()); - repositoryChanged = true; - } - } + var eventDirectory = new NPath(fileEvent.Directory); + var fileA = eventDirectory.Combine(fileEvent.FileA); - if (repositoryChanged) + NPath fileB = null; + if (fileEvent.FileB != null) { - Logger.Trace("RepositoryChanged"); - RepositoryChanged?.Invoke(); + fileB = eventDirectory.Combine(fileEvent.FileB); } - if (pauseEvent.Wait(200)) + // handling events in .git/* + if (fileA.IsChildOf(paths.DotGitPath)) { - break; + HandleEventInDotGit(fileEvent, fileA, fileB); + } + else + { + if (repositoryChanged || ignoredPaths.Any(ignoredPath => fileA.IsChildOf(ignoredPath))) + { + continue; + } + + repositoryChanged = true; } + lastCountOfProcessedEvents++; } + + if (repositoryChanged) + { + Logger.Trace("RepositoryChanged"); + RepositoryChanged?.Invoke(); + } + + processingEvents = false; + signalProcessingEventsDone.Set(); + return lastCountOfProcessedEvents; } private void HandleEventInDotGit(Event fileEvent, NPath fileA, NPath fileB = null) diff --git a/src/GitHub.Api/Git/IRepository.cs b/src/GitHub.Api/Git/IRepository.cs index 8dabc856e..12df52fef 100644 --- a/src/GitHub.Api/Git/IRepository.cs +++ b/src/GitHub.Api/Git/IRepository.cs @@ -8,6 +8,7 @@ namespace GitHub.Unity /// interface IRepository : IEquatable { + void Initialize(); void Refresh(); ITask SetupRemote(string remoteName, string remoteUrl); ITask Pull(); @@ -58,5 +59,6 @@ interface IRepository : IEquatable event Action OnLocalBranchListChanged; event Action OnHeadChanged; event Action> OnLocksUpdated; + event Action OnRepositoryInfoChanged; } } \ No newline at end of file diff --git a/src/GitHub.Api/Git/Repository.cs b/src/GitHub.Api/Git/Repository.cs index 1cebc96df..ff3aed1f6 100644 --- a/src/GitHub.Api/Git/Repository.cs +++ b/src/GitHub.Api/Git/Repository.cs @@ -19,6 +19,7 @@ class Repository : IRepository, IEquatable public event Action OnLocalBranchListChanged; public event Action OnHeadChanged; public event Action> OnLocksUpdated; + 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)); @@ -34,17 +35,18 @@ class Repository : IRepository, IEquatable /// The repository name. /// The repository's clone URL. /// - public Repository(IGitClient gitClient, IRepositoryManager repositoryManager, string name, UriString cloneUrl, NPath localPath) + public Repository(IGitClient gitClient, IRepositoryManager repositoryManager, NPath localPath) { Guard.ArgumentNotNull(repositoryManager, nameof(repositoryManager)); - Guard.ArgumentNotNullOrWhiteSpace(name, nameof(name)); - Guard.ArgumentNotNull(cloneUrl, nameof(cloneUrl)); this.gitClient = gitClient; this.repositoryManager = repositoryManager; - Name = name; - CloneUrl = cloneUrl; LocalPath = localPath; + if (repositoryManager.ActiveBranch.HasValue) + RepositoryManager_OnActiveBranchChanged(repositoryManager.ActiveBranch?.Name); + if (repositoryManager.ActiveRemote.HasValue) + RepositoryManager_OnActiveRemoteChanged(repositoryManager.ActiveRemote); + SetCloneUrl(); repositoryManager.OnStatusUpdated += RepositoryManager_OnStatusUpdated; repositoryManager.OnActiveBranchChanged += RepositoryManager_OnActiveBranchChanged; @@ -52,11 +54,17 @@ public Repository(IGitClient gitClient, IRepositoryManager repositoryManager, st repositoryManager.OnLocalBranchListChanged += RepositoryManager_OnLocalBranchListChanged; repositoryManager.OnHeadChanged += RepositoryManager_OnHeadChanged; repositoryManager.OnLocksUpdated += RepositoryManager_OnLocksUpdated; + repositoryManager.OnRemoteOrTrackingChanged += SetCloneUrl; + } - if (String.IsNullOrEmpty(CloneUrl)) - { - repositoryManager.OnRemoteOrTrackingChanged += RepositoryManager_OnRemoteOrTrackingChanged; ; - } + public void Initialize() + { + User = new User(); + gitClient.GetConfig("user.name", GitConfigSource.User) + .Then((s, x) => User.Name = x) + .Then(gitClient.GetConfig("user.email", GitConfigSource.User)) + .Then((s, x) => User.Email = x) + .Start(); } public void Refresh() @@ -113,34 +121,37 @@ public ITask ReleaseLock(string file, bool force) return repositoryManager.UnlockFile(file, force); } - private void RepositoryManager_OnRemoteOrTrackingChanged() + private void SetCloneUrl() { - var remote = repositoryManager.Config.GetRemotes() - .Where(x => HostAddress.Create(new UriString(x.Url).ToRepositoryUri()).IsGitHubDotCom()) - .FirstOrDefault(); - - if (remote.Url != null) - CloneUrl = new UriString(remote.Url).ToRepositoryUrl(); + if (CurrentRemote.HasValue) + CloneUrl = new UriString(CurrentRemote.Value.Url); + else + CloneUrl = null; + Name = CloneUrl != null ? CloneUrl.RepositoryName : LocalPath.FileName; + OnRepositoryInfoChanged?.Invoke(); } - private void RepositoryManager_OnHeadChanged() + private void RepositoryManager_OnActiveRemoteChanged(ConfigRemote? remote) { - OnHeadChanged?.Invoke(); + CurrentRemote = remote; + SetCloneUrl(); + OnActiveRemoteChanged?.Invoke(CurrentRemote.HasValue ? CurrentRemote.Value.Name : null); } - private void RepositoryManager_OnLocalBranchListChanged() + private void RepositoryManager_OnActiveBranchChanged(string branch) { - OnLocalBranchListChanged?.Invoke(); + CurrentBranch = branch; + OnActiveBranchChanged?.Invoke(CurrentBranch); } - private void RepositoryManager_OnActiveRemoteChanged() + private void RepositoryManager_OnHeadChanged() { - OnActiveRemoteChanged?.Invoke(CurrentRemote.Value.Name); + OnHeadChanged?.Invoke(); } - private void RepositoryManager_OnActiveBranchChanged() + private void RepositoryManager_OnLocalBranchListChanged() { - OnActiveBranchChanged?.Invoke(CurrentBranch); + OnLocalBranchListChanged?.Invoke(); } private void RepositoryManager_OnStatusUpdated(GitStatus status) @@ -162,7 +173,7 @@ private void RepositoryManager_OnLocksUpdated(IEnumerable locks) /// public override int GetHashCode() { - return 17 * 23 + (Name?.GetHashCode() ?? 0) * 23 + (Owner?.GetHashCode() ?? 0) * 23 + (LocalPath?.GetHashCode() ?? 0); + return LocalPath.GetHashCode(); } public override bool Equals(object obj) @@ -183,48 +194,41 @@ public bool Equals(IRepository other) if (ReferenceEquals(this, other)) return true; return other != null && - String.Equals(Name, other.Name) && - String.Equals(Owner, other.Owner) && - String.Equals(CloneUrl, other.CloneUrl) && object.Equals(LocalPath, other.LocalPath); } + public override string ToString() + { + return DebuggerDisplay; + } + /// /// Gets the current branch of the repository. /// - public string CurrentBranch - { - get - { - return repositoryManager.ActiveBranch?.Name; - } - } + public string CurrentBranch { get; private set; } /// /// Gets the current remote of the repository. /// - public ConfigRemote? CurrentRemote - { - get - { - return repositoryManager.ActiveRemote; - } - } + public ConfigRemote? CurrentRemote { get; private set; } public string Name { get; private set; } public UriString CloneUrl { get; private set; } public NPath LocalPath { get; private set; } - public string Owner => CloneUrl?.Owner ?? string.Empty; - public bool IsGitHub { get { return CloneUrl != ""; } } + public string Owner => CloneUrl?.Owner ?? null; + public bool IsGitHub { get { return HostAddress.IsGitHubDotCom(CloneUrl); } } internal string DebuggerDisplay => String.Format( CultureInfo.InvariantCulture, - "{4}\tOwner: {0} Name: {1} CloneUrl: {2} LocalPath: {3}", + "{0} Owner: {1} Name: {2} CloneUrl: {3} LocalPath: {4} Branch: {5} Remote: {6}", + GetHashCode(), Owner, Name, CloneUrl, LocalPath, - GetHashCode()); + CurrentBranch, + CurrentRemote?.Name + ); public GitStatus CurrentStatus { get; private set; } public IUser User { get; set; } diff --git a/src/GitHub.Api/Git/RepositoryManager.cs b/src/GitHub.Api/Git/RepositoryManager.cs index 5883fddf6..689a030b8 100644 --- a/src/GitHub.Api/Git/RepositoryManager.cs +++ b/src/GitHub.Api/Git/RepositoryManager.cs @@ -8,8 +8,8 @@ namespace GitHub.Unity { interface IRepositoryManager : IDisposable { - event Action OnActiveBranchChanged; - event Action OnActiveRemoteChanged; + event Action OnActiveBranchChanged; + event Action OnActiveRemoteChanged; event Action OnIsBusyChanged; event Action OnLocalBranchListChanged; event Action OnHeadChanged; @@ -17,7 +17,7 @@ interface IRepositoryManager : IDisposable event Action> OnLocksUpdated; event Action OnRemoteBranchListChanged; event Action OnRemoteOrTrackingChanged; - Task Initialize(); + void Initialize(); void Start(); void Stop(); void Refresh(); @@ -37,12 +37,12 @@ interface IRepositoryManager : IDisposable ITask UnlockFile(string file, bool force); Dictionary LocalBranches { get; } Dictionary> RemoteBranches { get; } - IRepository Repository { get; } IGitConfig Config { get; } ConfigBranch? ActiveBranch { get; } ConfigRemote? ActiveRemote { get; } IGitClient GitClient { get; } bool IsBusy { get; } + int WaitForEvents(); } interface IRepositoryPathConfiguration @@ -88,22 +88,6 @@ public RepositoryPathConfiguration(NPath repositoryPath) public NPath DotGitConfig { get; } } - class RepositoryManagerFactory - { - public RepositoryManager CreateRepositoryManager(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); - } - } - class RepositoryManager : IRepositoryManager { private readonly Dictionary branches = new Dictionary(); @@ -123,10 +107,10 @@ class RepositoryManager : IRepositoryManager private IEnumerable locks; private Dictionary> remoteBranches = new Dictionary>(); private Dictionary remotes; - private IRepository repository; + private Action repositoryUpdateCallback; - public event Action OnActiveBranchChanged; - public event Action OnActiveRemoteChanged; + public event Action OnActiveBranchChanged; + public event Action OnActiveRemoteChanged; public event Action OnHeadChanged; public event Action OnIsBusyChanged; public event Action OnLocalBranchListChanged; @@ -135,8 +119,21 @@ class RepositoryManager : IRepositoryManager public event Action OnRemoteOrTrackingChanged; public event Action OnStatusUpdated; - public RepositoryManager(IPlatform platform, ITaskManager taskManager, IUsageTracker usageTracker, - IGitConfig gitConfig, IRepositoryWatcher repositoryWatcher, IGitClient gitClient, + 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, IRepositoryPathConfiguration repositoryPaths, CancellationToken cancellationToken) { this.repositoryPaths = repositoryPaths; @@ -145,44 +142,17 @@ public RepositoryManager(IPlatform platform, ITaskManager taskManager, IUsageTra this.usageTracker = usageTracker; this.cancellationToken = cancellationToken; this.gitClient = gitClient; + this.watcher = repositoryWatcher; + this.config = gitConfig; - config = gitConfig; - - watcher = repositoryWatcher; - - watcher.HeadChanged += Watcher_OnHeadChanged; - watcher.IndexChanged += Watcher_OnIndexChanged; - watcher.ConfigChanged += Watcher_OnConfigChanged; - watcher.LocalBranchChanged += Watcher_OnLocalBranchChanged; - watcher.LocalBranchCreated += Watcher_OnLocalBranchCreated; - watcher.LocalBranchDeleted += Watcher_OnLocalBranchDeleted; - watcher.RepositoryChanged += Watcher_OnRepositoryChanged; - watcher.RemoteBranchCreated += Watcher_OnRemoteBranchCreated; - watcher.RemoteBranchDeleted += Watcher_OnRemoteBranchDeleted; - - var remote = config.GetRemote("origin"); - if (!remote.HasValue) - { - remote = config.GetRemotes() - .Where(x => HostAddress.Create(new UriString(x.Url).ToRepositoryUri()).IsGitHubDotCom()) - .FirstOrDefault(); - } - UriString cloneUrl = ""; - if (remote.Value.Url != null) - { - cloneUrl = new UriString(remote.Value.Url).ToRepositoryUrl(); - } - - repository = new Repository(gitClient, this, repositoryPaths.RepositoryPath.FileName, cloneUrl, - repositoryPaths.RepositoryPath); + SetupConfig(repositoryPaths); + SetupWatcher(); } - public async Task Initialize() + public void Initialize() { Logger.Trace("Initialize"); watcher.Initialize(); - repository = await InitializeRepository().SafeAwait(); - Logger.Trace($"Initialize done {Repository}"); } public void Start() @@ -197,6 +167,17 @@ public void Stop() watcher.Stop(); } + /// + /// Never ever call this from any callback that might be triggered by events + /// raised here. This is not reentrancy safe and will deadlock if you do. + /// Call this only from a non-callback main thread or preferably only for tests + /// + /// + public int WaitForEvents() + { + return watcher.CheckAndProcessEvents(); + } + public void Refresh() { Logger.Trace("Refresh"); @@ -314,16 +295,30 @@ public ITask UnlockFile(string file, bool force) return task; } + private void SetupWatcher() + { + watcher.HeadChanged += Watcher_OnHeadChanged; + watcher.IndexChanged += Watcher_OnIndexChanged; + watcher.ConfigChanged += Watcher_OnConfigChanged; + watcher.LocalBranchChanged += Watcher_OnLocalBranchChanged; + watcher.LocalBranchCreated += Watcher_OnLocalBranchCreated; + watcher.LocalBranchDeleted += Watcher_OnLocalBranchDeleted; + watcher.RepositoryChanged += Watcher_OnRepositoryChanged; + watcher.RemoteBranchCreated += Watcher_OnRemoteBranchCreated; + watcher.RemoteBranchDeleted += Watcher_OnRemoteBranchDeleted; + } + + private void SetupConfig(IRepositoryPathConfiguration repositoryPaths) + { + this.head = repositoryPaths.DotGitHead.ReadAllLines().FirstOrDefault(); + RefreshConfigData(); + } + private ITask HookupHandlers(ITask task, bool disableWatcher = false) { task.OnStart += t => { Logger.Trace("Start " + task.Name); - if (IsBusy) - { - throw new Exception("System Busy"); - } - IsBusy = true; if (disableWatcher) @@ -399,7 +394,8 @@ private void Watcher_OnHeadChanged(string contents) } private void Watcher_OnIndexChanged() - { } + { + } private void Watcher_OnLocalBranchCreated(string name) { @@ -415,33 +411,11 @@ private void Watcher_OnLocalBranchChanged(string name) { if (name == ActiveBranch?.Name) { - OnActiveBranchChanged?.Invoke(); + OnActiveBranchChanged?.Invoke(name); UpdateGitStatus(); } } - private async Task InitializeRepository() - { - head = repositoryPaths.DotGitHead.ReadAllLines().FirstOrDefault(); - - RefreshConfigData(); - - var user = new User(); - - var res = await GitClient.GetConfig("user.name", GitConfigSource.User).StartAwait(); - user.Name = res; - - res = await gitClient.GetConfig("user.email", GitConfigSource.User).StartAwait(); - if (res == null) - { - throw new InvalidOperationException("No user configured"); - } - - user.Email = res; - repository.User = user; - return repository; - } - private void RefreshConfigData() { Logger.Trace("RefreshConfigData"); @@ -622,7 +596,6 @@ public void Dispose() public Dictionary LocalBranches => branches; public Dictionary> RemoteBranches => remoteBranches; - public IRepository Repository => repository; public IGitConfig Config => config; public ConfigBranch? ActiveBranch @@ -630,12 +603,11 @@ public ConfigBranch? ActiveBranch get { return activeBranch; } private set { - if (activeBranch.HasValue != value.HasValue || - activeBranch.HasValue && !activeBranch.Value.Equals(value.Value)) + if (activeBranch.HasValue != value.HasValue || (activeBranch.HasValue && !activeBranch.Value.Equals(value.Value))) { activeBranch = value; Logger.Trace("OnActiveBranchChanged: {0}", value?.ToString() ?? "NULL"); - OnActiveBranchChanged?.Invoke(); + OnActiveBranchChanged?.Invoke(activeBranch.Value.Name); } } } @@ -645,12 +617,11 @@ public ConfigRemote? ActiveRemote get { return activeRemote; } private set { - if (activeRemote.HasValue != value.HasValue || - activeRemote.HasValue && !activeRemote.Value.Equals(value.Value)) + if (activeRemote.HasValue != value.HasValue || (activeRemote.HasValue && !activeRemote.Value.Equals(value.Value))) { activeRemote = value; Logger.Trace("OnActiveRemoteChanged: {0}", value?.ToString() ?? "NULL"); - OnActiveRemoteChanged?.Invoke(); + OnActiveRemoteChanged?.Invoke(activeRemote); } } } diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/ApplicationManager.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/ApplicationManager.cs index 0e93f28e0..431fee511 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/ApplicationManager.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/ApplicationManager.cs @@ -23,11 +23,7 @@ public ApplicationManager(IMainThreadSynchronizationContext synchronizationConte protected override void SetupMetrics() { - - new ActionTask(CancellationToken, - () => SetupMetrics(Environment.UnityVersion, ApplicationCache.Instance.FirstRun)) - { Affinity = TaskAffinity.UI } - .Start(); + SetupMetrics(Environment.UnityVersion, ApplicationCache.Instance.FirstRun); } protected override void InitializeUI() diff --git a/src/tests/IntegrationTests/BaseGitEnvironmentTest.cs b/src/tests/IntegrationTests/BaseGitEnvironmentTest.cs index ffe598713..a45d95bf4 100644 --- a/src/tests/IntegrationTests/BaseGitEnvironmentTest.cs +++ b/src/tests/IntegrationTests/BaseGitEnvironmentTest.cs @@ -30,12 +30,12 @@ protected async Task Initialize(NPath repoPath, NPath environmentP var usageTracker = new NullUsageTracker(); - var repositoryManagerFactory = new RepositoryManagerFactory(); - RepositoryManager = repositoryManagerFactory.CreateRepositoryManager(Platform, TaskManager, usageTracker, GitClient, repoPath); - await RepositoryManager.Initialize(); - RepositoryManager.Start(); + RepositoryManager = GitHub.Unity.RepositoryManager.CreateInstance(Platform, TaskManager, usageTracker, GitClient, repoPath); + RepositoryManager.Initialize(); + + Environment.Repository = new Repository(GitClient, RepositoryManager, repoPath); - Environment.Repository = RepositoryManager.Repository; + RepositoryManager.Start(); DotGitPath = repoPath.Combine(".git"); diff --git a/src/tests/IntegrationTests/BaseGitRepoTest.cs b/src/tests/IntegrationTests/BaseGitRepoTest.cs index ce0c7dc7c..57ed1b536 100644 --- a/src/tests/IntegrationTests/BaseGitRepoTest.cs +++ b/src/tests/IntegrationTests/BaseGitRepoTest.cs @@ -14,18 +14,16 @@ public override void OnSetup() TestRepoMasterCleanUnsynchronizedRussianLanguage = TestBasePath.Combine("IOTestsRepo", "IOTestsRepo_master_clean_sync_with_russian_language"); TestRepoMasterCleanSynchronized = TestBasePath.Combine("IOTestsRepo", "IOTestsRepo_master_clean_sync"); TestRepoMasterDirtyUnsynchronized = TestBasePath.Combine("IOTestsRepo", "IOTestsRepo_master_dirty_unsync"); + TestRepoMasterTwoRemotes = TestBasePath.Combine("IOTestsRepo", "IOTestsRepo_master_two_remotes"); ZipHelper.ExtractZipFile(TestZipFilePath, TestBasePath.ToString(), CancellationToken.None); } protected NPath TestRepoMasterCleanSynchronized { get; private set; } - protected NPath TestRepoMasterCleanUnsynchronized { get; private set; } - protected NPath TestRepoMasterCleanUnsynchronizedRussianLanguage { get; private set; } - protected NPath TestRepoMasterDirtyUnsynchronized { get; private set; } - + protected NPath TestRepoMasterTwoRemotes { get; private set; } private static string TestZipFilePath => Path.Combine(SolutionDirectory, "IOTestsRepo.zip"); } } diff --git a/src/tests/IntegrationTests/Events/RepositoryManagerTests.cs b/src/tests/IntegrationTests/Events/RepositoryManagerTests.cs index e40540a0f..3cb91dbcf 100644 --- a/src/tests/IntegrationTests/Events/RepositoryManagerTests.cs +++ b/src/tests/IntegrationTests/Events/RepositoryManagerTests.cs @@ -13,15 +13,21 @@ namespace IntegrationTests [TestFixture, Category("TimeSensitive")] class RepositoryManagerTests : BaseGitEnvironmentTest { + private RepositoryManagerEvents repositoryManagerEvents; + + public override void OnSetup() + { + base.OnSetup(); + repositoryManagerEvents = new RepositoryManagerEvents(); + } + [Test] public async Task ShouldDetectFileChanges() { await Initialize(TestRepoMasterCleanSynchronized); - var managerAutoResetEvent = new RepositoryManagerAutoResetEvent(); - var repositoryManagerListener = Substitute.For(); - repositoryManagerListener.AttachListener(RepositoryManager, managerAutoResetEvent); + repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); var expected = new GitStatus { Behind = 1, @@ -37,26 +43,18 @@ public async Task ShouldDetectFileChanges() var result = new GitStatus(); RepositoryManager.OnStatusUpdated += status => { result = status; }; - Logger.Trace("Issuing Changes"); - var foobarTxt = TestRepoMasterCleanSynchronized.Combine("foobar.txt"); foobarTxt.WriteAllText("foobar"); await TaskManager.Wait(); - // give the fs watcher a bit of time to catch up - await TaskEx.Delay(200); - await TaskManager.Wait(); - - managerAutoResetEvent.OnRepositoryChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue(); - - Logger.Trace("Continue test"); + RepositoryManager.WaitForEvents(); + WaitForNotBusy(repositoryManagerEvents, 1); repositoryManagerListener.Received().OnRepositoryChanged(Args.GitStatus); result.AssertEqual(expected); - repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(); + repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Args.String); + repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); repositoryManagerListener.DidNotReceive().OnHeadChanged(); repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); @@ -69,10 +67,8 @@ public async Task ShouldAddAndCommitFiles() { await Initialize(TestRepoMasterCleanSynchronized); - var managerAutoResetEvent = new RepositoryManagerAutoResetEvent(); - var repositoryManagerListener = Substitute.For(); - repositoryManagerListener.AttachListener(RepositoryManager, managerAutoResetEvent); + repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); var expectedAfterChanges = new GitStatus { Behind = 1, @@ -101,25 +97,22 @@ public async Task ShouldAddAndCommitFiles() result = status; }; - Logger.Trace("Issuing Changes"); - var foobarTxt = TestRepoMasterCleanSynchronized.Combine("foobar.txt"); foobarTxt.WriteAllText("foobar"); var testDocumentTxt = TestRepoMasterCleanSynchronized.Combine("Assets", "TestDocument.txt"); testDocumentTxt.WriteAllText("foobar"); await TaskManager.Wait(); - - managerAutoResetEvent.OnRepositoryChanged.WaitOne(TimeSpan.FromSeconds(200)).Should().BeTrue(); - - Logger.Trace("Continue test"); + WaitForNotBusy(repositoryManagerEvents, 1); + RepositoryManager.WaitForEvents(); + WaitForNotBusy(repositoryManagerEvents, 1); repositoryManagerListener.Received().OnRepositoryChanged(Args.GitStatus); result.AssertEqual(expectedAfterChanges); repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(); + repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Args.String); + repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); repositoryManagerListener.DidNotReceive().OnHeadChanged(); repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); @@ -127,21 +120,18 @@ public async Task ShouldAddAndCommitFiles() repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); repositoryManagerListener.ClearReceivedCalls(); - - Logger.Trace("Issuing Command"); + repositoryManagerEvents.Reset(); await RepositoryManager .CommitFiles(new List() { "Assets\\TestDocument.txt", "foobar.txt" }, "IntegrationTest Commit", string.Empty) .StartAsAsync(); await TaskManager.Wait(); + RepositoryManager.WaitForEvents(); + WaitForNotBusy(repositoryManagerEvents, 1); - managerAutoResetEvent.OnActiveBranchChanged.WaitOne(TimeSpan.FromSeconds(5)).Should().BeTrue(); - - Logger.Trace("Continue test"); - - repositoryManagerListener.Received(1).OnActiveBranchChanged(); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(); + repositoryManagerListener.Received(1).OnActiveBranchChanged(Args.String); + repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); repositoryManagerListener.DidNotReceive().OnHeadChanged(); repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); @@ -154,10 +144,8 @@ public async Task ShouldDetectBranchChange() { await Initialize(TestRepoMasterCleanSynchronized); - var managerAutoResetEvent = new RepositoryManagerAutoResetEvent(); - var repositoryManagerListener = Substitute.For(); - repositoryManagerListener.AttachListener(RepositoryManager, managerAutoResetEvent); + repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); var expected = new GitStatus { LocalBranch = "feature/document", @@ -168,27 +156,19 @@ public async Task ShouldDetectBranchChange() var result = new GitStatus(); RepositoryManager.OnStatusUpdated += status => { result = status; }; - Logger.Trace("Issuing Command"); - await RepositoryManager.SwitchBranch("feature/document").StartAsAsync(); await TaskManager.Wait(); + RepositoryManager.WaitForEvents(); - // give the fs watcher a bit of time to catch up - await TaskEx.Delay(100); - await TaskManager.Wait(); - - managerAutoResetEvent.OnActiveBranchChanged.WaitOne(TimeSpan.FromSeconds(3)).Should().BeTrue(); - managerAutoResetEvent.OnRepositoryChanged.WaitOne(TimeSpan.FromSeconds(3)).Should().BeTrue(); - managerAutoResetEvent.OnHeadChanged.WaitOne(TimeSpan.FromSeconds(3)).Should().BeTrue(); - - Logger.Trace("Continue test"); + repositoryManagerListener.Received(1).OnActiveBranchChanged(Args.String); + repositoryManagerListener.Received(1).OnHeadChanged(); + repositoryManagerEvents.Reset(); + WaitForNotBusy(repositoryManagerEvents, 1); repositoryManagerListener.Received().OnRepositoryChanged(Args.GitStatus); result.AssertEqual(expected); - repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.Received(1).OnActiveBranchChanged(); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(); - repositoryManagerListener.Received(1).OnHeadChanged(); + + repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); repositoryManagerListener.DidNotReceive().OnRemoteOrTrackingChanged(); @@ -200,25 +180,18 @@ public async Task ShouldDetectBranchDelete() { await Initialize(TestRepoMasterCleanSynchronized); - var managerAutoResetEvent = new RepositoryManagerAutoResetEvent(); - var repositoryManagerListener = Substitute.For(); - repositoryManagerListener.AttachListener(RepositoryManager, managerAutoResetEvent); - - Logger.Trace("Issuing Command"); + repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); await RepositoryManager.DeleteBranch("feature/document", true).StartAsAsync(); await TaskManager.Wait(); - - managerAutoResetEvent.OnLocalBranchListChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue(); - managerAutoResetEvent.OnRemoteOrTrackingChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue(); - - Logger.Trace("Continue test"); + RepositoryManager.WaitForEvents(); + WaitForNotBusy(repositoryManagerEvents, 1); repositoryManagerListener.DidNotReceive().OnRepositoryChanged(Args.GitStatus); repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(); + repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Args.String); + repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); repositoryManagerListener.DidNotReceive().OnHeadChanged(); repositoryManagerListener.Received(1).OnLocalBranchListChanged(); repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); @@ -231,24 +204,17 @@ public async Task ShouldDetectBranchCreate() { await Initialize(TestRepoMasterCleanSynchronized); - var managerAutoResetEvent = new RepositoryManagerAutoResetEvent(); - var repositoryManagerListener = Substitute.For(); - repositoryManagerListener.AttachListener(RepositoryManager, managerAutoResetEvent); - - Logger.Trace("Issuing Command"); + repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); await RepositoryManager.CreateBranch("feature/document2", "feature/document").StartAsAsync(); await TaskManager.Wait(); - - managerAutoResetEvent.OnLocalBranchListChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue(); - - Logger.Trace("Continue test"); + RepositoryManager.WaitForEvents(); repositoryManagerListener.DidNotReceive().OnRepositoryChanged(Args.GitStatus); repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(); + repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Args.String); + repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); repositoryManagerListener.DidNotReceive().OnHeadChanged(); repositoryManagerListener.Received(1).OnLocalBranchListChanged(); repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); @@ -256,26 +222,20 @@ public async Task ShouldDetectBranchCreate() repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); repositoryManagerListener.ClearReceivedCalls(); - - Logger.Trace("Issuing Command"); + repositoryManagerEvents.Reset(); await RepositoryManager.CreateBranch("feature2/document2", "feature/document").StartAsAsync(); await TaskManager.Wait(); - - managerAutoResetEvent.OnLocalBranchListChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue(); - - Logger.Trace("Continue test"); + RepositoryManager.WaitForEvents(); repositoryManagerListener.DidNotReceive().OnRepositoryChanged(Args.GitStatus); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(); + repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Args.String); + repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); repositoryManagerListener.DidNotReceive().OnHeadChanged(); repositoryManagerListener.Received(1).OnLocalBranchListChanged(); repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); repositoryManagerListener.DidNotReceive().OnRemoteOrTrackingChanged(); repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); - - repositoryManagerListener.ClearReceivedCalls(); } [Test] @@ -283,26 +243,29 @@ public async Task ShouldDetectChangesToRemotes() { await Initialize(TestRepoMasterCleanSynchronized); - var managerAutoResetEvent = new RepositoryManagerAutoResetEvent(); - var repositoryManagerListener = Substitute.For(); - repositoryManagerListener.AttachListener(RepositoryManager, managerAutoResetEvent); + repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); - Logger.Trace("Issuing Command"); + RepositoryManager.ActiveRemote.HasValue.Should().BeTrue(); + RepositoryManager.ActiveRemote.Value.Name.Should().Be("origin"); + RepositoryManager.ActiveRemote.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(); - managerAutoResetEvent.OnActiveBranchChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue(); - managerAutoResetEvent.OnActiveRemoteChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue(); - managerAutoResetEvent.OnRemoteOrTrackingChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue(); + RepositoryManager.ActiveRemote.HasValue.Should().BeFalse(); - Logger.Trace("Continue test"); + Environment.Repository.CloneUrl.Should().BeNull(); + Environment.Repository.Owner.Should().BeNull(); repositoryManagerListener.DidNotReceive().OnRepositoryChanged(Args.GitStatus); repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.Received().OnActiveBranchChanged(); - repositoryManagerListener.Received().OnActiveRemoteChanged(); + repositoryManagerListener.Received().OnActiveBranchChanged(Args.String); + repositoryManagerListener.Received().OnActiveRemoteChanged(Arg.Any()); repositoryManagerListener.DidNotReceive().OnHeadChanged(); repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); @@ -310,21 +273,126 @@ public async Task ShouldDetectChangesToRemotes() 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(); + + RepositoryManager.ActiveRemote.HasValue.Should().BeTrue(); + RepositoryManager.ActiveRemote.Value.Name.Should().Be("origin"); + RepositoryManager.ActiveRemote.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().OnRepositoryChanged(Args.GitStatus); + repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Args.String); + repositoryManagerListener.Received().OnActiveRemoteChanged(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnHeadChanged(); + repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); + repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); + repositoryManagerListener.Received().OnRemoteOrTrackingChanged(); + repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); + } + + [Test] + public async Task ShouldDetectChangesToRemotesWhenSwitchingBranches() + { + var expectedCloneUrl = "https://github.com/EvilStanleyGoldman/IOTestsRepo.git"; + + await Initialize(TestRepoMasterTwoRemotes); + + var repositoryManagerListener = Substitute.For(); + repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); - Logger.Trace("Issuing Command"); + RepositoryManager.ActiveRemote.HasValue.Should().BeTrue(); + RepositoryManager.ActiveRemote.Value.Name.Should().Be("origin"); + Assert.AreEqual(expectedCloneUrl, RepositoryManager.ActiveRemote.Value.Url, "Remote is wrong for initial test setup"); + Assert.AreEqual(expectedCloneUrl, Environment.Repository.CloneUrl?.ToString(), "CloneUrl is wrong for initial test setup"); + Environment.Repository.Owner.Should().Be("EvilStanleyGoldman"); - await RepositoryManager.RemoteAdd("origin", "https://github.com/EvilStanleyGoldman/IOTestsRepo.git").StartAsAsync(); + await RepositoryManager.CreateBranch("branch2", "another/master") + .Then(RepositoryManager.SwitchBranch("branch2")) + .StartAsAsync(); await TaskManager.Wait(); + RepositoryManager.WaitForEvents(); + + RepositoryManager.ActiveRemote.HasValue.Should().BeTrue(); + RepositoryManager.ActiveRemote.Value.Name.Should().Be("another"); + var expectedRemoteUrl = "https://another.remote/Owner/Url.git"; - managerAutoResetEvent.OnActiveRemoteChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue(); - managerAutoResetEvent.OnRemoteOrTrackingChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue(); + Assert.AreEqual(expectedRemoteUrl, RepositoryManager.ActiveRemote.Value.Url, "Remote is wrong"); + Assert.AreEqual(expectedRemoteUrl, Environment.Repository.CloneUrl?.ToString(), "CloneUrl is wrong"); + Environment.Repository.Owner.Should().Be("Owner"); - Logger.Trace("Continue test"); + repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); + // this should probably not be triggered + repositoryManagerListener.Received().OnLocalBranchListChanged(); + repositoryManagerListener.Received().OnRemoteOrTrackingChanged(); + repositoryManagerListener.Received().OnActiveBranchChanged(Args.String); + repositoryManagerListener.Received().OnActiveRemoteChanged(Arg.Any()); + repositoryManagerListener.Received().OnHeadChanged(); + + // this gets triggered by the active head changing so it may not happen on time for us to check + repositoryManagerListener.DidNotReceive().OnRepositoryChanged(Args.GitStatus); + repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); + repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); + } + + [Test] + public async Task ShouldUpdateCloneUrlIfRemoteIsDeleted() + { + await Initialize(TestRepoMasterCleanSynchronized); + + var repositoryManagerListener = Substitute.For(); + repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); + + RepositoryManager.ActiveRemote.HasValue.Should().BeTrue(); + RepositoryManager.ActiveRemote.Value.Name.Should().Be("origin"); + RepositoryManager.ActiveRemote.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(); + + RepositoryManager.ActiveRemote.HasValue.Should().BeFalse(); + + Environment.Repository.CloneUrl.Should().BeNull(); + Environment.Repository.Owner.Should().BeNull(); + + repositoryManagerListener.DidNotReceive().OnRepositoryChanged(Args.GitStatus); + repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); + repositoryManagerListener.Received().OnActiveBranchChanged(Args.String); + repositoryManagerListener.Received().OnActiveRemoteChanged(Arg.Any()); + repositoryManagerListener.DidNotReceive().OnHeadChanged(); + repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); + repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); + repositoryManagerListener.Received().OnRemoteOrTrackingChanged(); + 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(); + + RepositoryManager.ActiveRemote.HasValue.Should().BeTrue(); + RepositoryManager.ActiveRemote.Value.Name.Should().Be("origin"); + RepositoryManager.ActiveRemote.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().OnRepositoryChanged(Args.GitStatus); repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(); - repositoryManagerListener.Received().OnActiveRemoteChanged(); + repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Args.String); + repositoryManagerListener.Received().OnActiveRemoteChanged(Arg.Any()); repositoryManagerListener.DidNotReceive().OnHeadChanged(); repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); @@ -337,10 +405,8 @@ public async Task ShouldDetectGitPull() { await Initialize(TestRepoMasterCleanSynchronized); - var managerAutoResetEvent = new RepositoryManagerAutoResetEvent(); - var repositoryManagerListener = Substitute.For(); - repositoryManagerListener.AttachListener(RepositoryManager, managerAutoResetEvent); + repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); var expected = new GitStatus { LocalBranch = "master", @@ -351,27 +417,18 @@ public async Task ShouldDetectGitPull() var result = new GitStatus(); RepositoryManager.OnStatusUpdated += status => { result = status; }; - Logger.Trace("Issuing Command"); - await RepositoryManager.Pull("origin", "master").StartAsAsync(); await TaskManager.Wait(); + RepositoryManager.WaitForEvents(); - managerAutoResetEvent.OnRepositoryChanged.WaitOne(TimeSpan.FromSeconds(7)).Should().BeTrue(); - managerAutoResetEvent.OnActiveBranchChanged.WaitOne(TimeSpan.FromSeconds(7)).Should().BeTrue(); - - WaitForNotBusy(managerAutoResetEvent, 3); - WaitForNotBusy(managerAutoResetEvent, 3); - - Logger.Trace("Continue test"); - - repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); - RepositoryManager.IsBusy.Should().BeFalse(); + repositoryManagerListener.Received(1).OnActiveBranchChanged(Args.String); + repositoryManagerEvents.Reset(); + WaitForNotBusy(repositoryManagerEvents, 1); repositoryManagerListener.Received().OnRepositoryChanged(Args.GitStatus); result.AssertEqual(expected); - repositoryManagerListener.Received(1).OnActiveBranchChanged(); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(); + repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); repositoryManagerListener.DidNotReceive().OnHeadChanged(); repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); @@ -384,25 +441,17 @@ public async Task ShouldDetectGitFetch() { await Initialize(TestRepoMasterCleanUnsynchronized); - var managerAutoResetEvent = new RepositoryManagerAutoResetEvent(); - var repositoryManagerListener = Substitute.For(); - repositoryManagerListener.AttachListener(RepositoryManager, managerAutoResetEvent); - - Logger.Trace("Issuing Command"); + repositoryManagerListener.AttachListener(RepositoryManager, repositoryManagerEvents); await RepositoryManager.Fetch("origin").StartAsAsync(); await TaskManager.Wait(); - - managerAutoResetEvent.OnRemoteBranchListChanged.WaitOne(TimeSpan.FromSeconds(3)).Should().BeTrue(); - managerAutoResetEvent.OnRemoteBranchListChanged.WaitOne(TimeSpan.FromSeconds(3)).Should().BeTrue(); - - Logger.Trace("Continue test"); + RepositoryManager.WaitForEvents(); repositoryManagerListener.DidNotReceive().OnRepositoryChanged(Args.GitStatus); repositoryManagerListener.ReceivedWithAnyArgs().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(); + repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Args.String); + repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); repositoryManagerListener.DidNotReceive().OnHeadChanged(); repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); repositoryManagerListener.Received(2).OnRemoteBranchListChanged(); @@ -410,13 +459,10 @@ public async Task ShouldDetectGitFetch() repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock); } - private void WaitForNotBusy(RepositoryManagerAutoResetEvent managerAutoResetEvent, int seconds = 1) + private void WaitForNotBusy(RepositoryManagerEvents managerEvents, int seconds = 1) { - if (RepositoryManager.IsBusy) - { - Logger.Trace("Waiting for activity", seconds); - managerAutoResetEvent.OnIsBusyChanged.WaitOne(TimeSpan.FromSeconds(seconds)); - } + managerEvents.OnIsBusy.WaitOne(TimeSpan.FromSeconds(seconds)); + managerEvents.OnIsNotBusy.WaitOne(TimeSpan.FromSeconds(seconds)); } } } diff --git a/src/tests/IntegrationTests/Git/IntegrationTestEnvironment.cs b/src/tests/IntegrationTests/Git/IntegrationTestEnvironment.cs index a09b11709..10e0c8ebe 100644 --- a/src/tests/IntegrationTests/Git/IntegrationTestEnvironment.cs +++ b/src/tests/IntegrationTests/Git/IntegrationTestEnvironment.cs @@ -26,7 +26,7 @@ public IntegrationTestEnvironment(NPath repoPath, NPath solutionDirectory, NPath UserCachePath = integrationTestEnvironmentPath.Combine("User"); SystemCachePath = integrationTestEnvironmentPath.Combine("System"); - var installPath = solutionDirectory.Parent.Parent.Parent.Combine("GitHub.Api"); + var installPath = solutionDirectory.Parent.Parent.Combine("src", "GitHub.Api"); Initialize(UnityVersion, installPath, solutionDirectory, repoPath.Combine("Assets")); InitializeRepository(); diff --git a/src/tests/IntegrationTests/IOTestsRepo.zip b/src/tests/IntegrationTests/IOTestsRepo.zip index 6916e5c81..1b7560cd6 100644 --- a/src/tests/IntegrationTests/IOTestsRepo.zip +++ b/src/tests/IntegrationTests/IOTestsRepo.zip @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18ef06487ae4eb39c2e2eb9a4199df92950dabda686e294a98003662171fd6f8 -size 236036 +oid sha256:0e8614b3fb2545993c4539dd3967df29072386c523d4707ec579a91a6a4efaf9 +size 296148 diff --git a/src/tests/TestUtils/Events/IRepositoryManagerListener.cs b/src/tests/TestUtils/Events/IRepositoryManagerListener.cs index 5b60a0566..116ce7151 100644 --- a/src/tests/TestUtils/Events/IRepositoryManagerListener.cs +++ b/src/tests/TestUtils/Events/IRepositoryManagerListener.cs @@ -8,98 +8,116 @@ namespace TestUtils.Events { interface IRepositoryManagerListener { - void OnRepositoryChanged(GitStatus obj); - void OnActiveBranchChanged(); - void OnActiveRemoteChanged(); + void OnRepositoryChanged(GitStatus status); + void OnActiveBranchChanged(string branch); + void OnActiveRemoteChanged(ConfigRemote? remote); void OnHeadChanged(); void OnLocalBranchListChanged(); void OnRemoteBranchListChanged(); void OnRemoteOrTrackingChanged(); - void OnIsBusyChanged(bool obj); + void OnIsBusyChanged(bool busy); void OnLocksUpdated(IEnumerable locks); } - class RepositoryManagerAutoResetEvent + class RepositoryManagerEvents { - public AutoResetEvent OnIsBusyChanged { get; } = new AutoResetEvent(false); - public AutoResetEvent OnRepositoryChanged { get; } = new AutoResetEvent(false); - public AutoResetEvent OnActiveBranchChanged { get; } = new AutoResetEvent(false); - public AutoResetEvent OnActiveRemoteChanged { get; } = new AutoResetEvent(false); - public AutoResetEvent OnHeadChanged { get; } = new AutoResetEvent(false); - public AutoResetEvent OnLocalBranchListChanged { get; } = new AutoResetEvent(false); - public AutoResetEvent OnRemoteBranchListChanged { get; } = new AutoResetEvent(false); - public AutoResetEvent OnRemoteOrTrackingChanged { get; } = new AutoResetEvent(false); - public AutoResetEvent OnLocksUpdated { get; } = new AutoResetEvent(false); + public EventWaitHandle OnIsBusy { get; } = new ManualResetEvent(false); + public EventWaitHandle OnIsNotBusy { get; } = new ManualResetEvent(false); + public EventWaitHandle OnRepositoryChanged { get; } = new ManualResetEvent(false); + public EventWaitHandle OnActiveBranchChanged { get; } = new ManualResetEvent(false); + public EventWaitHandle OnActiveRemoteChanged { get; } = new ManualResetEvent(false); + public EventWaitHandle OnHeadChanged { get; } = new ManualResetEvent(false); + public EventWaitHandle OnLocalBranchListChanged { get; } = new ManualResetEvent(false); + public EventWaitHandle OnRemoteBranchListChanged { get; } = new ManualResetEvent(false); + public EventWaitHandle OnRemoteOrTrackingChanged { get; } = new ManualResetEvent(false); + public EventWaitHandle OnLocksUpdated { get; } = new ManualResetEvent(false); + + public void Reset() + { + OnIsBusy.Reset(); + OnIsNotBusy.Reset(); + OnRepositoryChanged.Reset(); + OnActiveBranchChanged.Reset(); + OnActiveRemoteChanged.Reset(); + OnHeadChanged.Reset(); + OnLocalBranchListChanged.Reset(); + OnRemoteBranchListChanged.Reset(); + OnRemoteOrTrackingChanged.Reset(); + OnLocksUpdated.Reset(); + } } static class RepositoryManagerListenerExtensions { public static void AttachListener(this IRepositoryManagerListener listener, IRepositoryManager repositoryManager, - RepositoryManagerAutoResetEvent managerAutoResetEvent = null, bool trace = false) + RepositoryManagerEvents managerEvents = null, bool trace = true) { var logger = trace ? Logging.GetLogger() : null; repositoryManager.OnIsBusyChanged += b => { logger?.Trace("OnIsBusyChanged: {0}", b); listener.OnIsBusyChanged(b); - managerAutoResetEvent?.OnIsBusyChanged.Set(); + if (b) + managerEvents?.OnIsBusy.Set(); + else + managerEvents?.OnIsNotBusy.Set(); }; repositoryManager.OnStatusUpdated += status => { logger?.Debug("OnStatusUpdated: {0}", status); listener.OnRepositoryChanged(status); - managerAutoResetEvent?.OnRepositoryChanged.Set(); + managerEvents?.OnRepositoryChanged.Set(); }; - repositoryManager.OnActiveBranchChanged += () => { - logger?.Trace("OnActiveBranchChanged"); - listener.OnActiveBranchChanged(); - managerAutoResetEvent?.OnActiveBranchChanged.Set(); + repositoryManager.OnActiveBranchChanged += branch => { + logger?.Trace($"OnActiveBranchChanged {branch}"); + listener.OnActiveBranchChanged(branch); + managerEvents?.OnActiveBranchChanged.Set(); }; - repositoryManager.OnActiveRemoteChanged += () => { - logger?.Trace("OnActiveRemoteChanged"); - listener.OnActiveRemoteChanged(); - managerAutoResetEvent?.OnActiveRemoteChanged.Set(); + repositoryManager.OnActiveRemoteChanged += remote => { + logger?.Trace($"OnActiveRemoteChanged {(remote.HasValue ? remote.Value.Name : null)}"); + listener.OnActiveRemoteChanged(remote); + managerEvents?.OnActiveRemoteChanged.Set(); }; repositoryManager.OnHeadChanged += () => { - logger?.Trace("OnHeadChanged"); + logger?.Trace($"OnHeadChanged"); listener.OnHeadChanged(); - managerAutoResetEvent?.OnHeadChanged.Set(); + managerEvents?.OnHeadChanged.Set(); }; repositoryManager.OnLocalBranchListChanged += () => { logger?.Trace("OnLocalBranchListChanged"); listener.OnLocalBranchListChanged(); - managerAutoResetEvent?.OnLocalBranchListChanged.Set(); + managerEvents?.OnLocalBranchListChanged.Set(); }; repositoryManager.OnRemoteBranchListChanged += () => { logger?.Trace("OnRemoteBranchListChanged"); listener.OnRemoteBranchListChanged(); - managerAutoResetEvent?.OnRemoteBranchListChanged.Set(); + managerEvents?.OnRemoteBranchListChanged.Set(); }; repositoryManager.OnRemoteOrTrackingChanged += () => { logger?.Trace("OnRemoteOrTrackingChanged"); listener.OnRemoteOrTrackingChanged(); - managerAutoResetEvent?.OnRemoteOrTrackingChanged.Set(); + managerEvents?.OnRemoteOrTrackingChanged.Set(); }; repositoryManager.OnLocksUpdated += locks => { var lockArray = locks.ToArray(); logger?.Trace("OnLocksUpdated Count:{0}", lockArray.Length); listener.OnLocksUpdated(lockArray); - managerAutoResetEvent?.OnLocksUpdated.Set(); + managerEvents?.OnLocksUpdated.Set(); }; } public static void AssertDidNotReceiveAnyCalls(this IRepositoryManagerListener repositoryManagerListener) { repositoryManagerListener.DidNotReceive().OnRepositoryChanged(Args.GitStatus); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(); + repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Args.String); + repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); repositoryManagerListener.DidNotReceive().OnHeadChanged(); repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); diff --git a/src/tests/UnitTests/Repository/RepositoryManagerTests.cs b/src/tests/UnitTests/Repository/RepositoryManagerTests.cs index ca4e726de..ed6362efe 100644 --- a/src/tests/UnitTests/Repository/RepositoryManagerTests.cs +++ b/src/tests/UnitTests/Repository/RepositoryManagerTests.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Threading; using FluentAssertions; @@ -162,7 +161,7 @@ public async Task ShouldRefreshAndReturnCombinedStatusAndLockInformation1() var repositoryProcessRunner = CreateRepositoryProcessRunner(responseGitStatus, responseGitLocks); var repositoryManager = CreateRepositoryManager(repositoryProcessRunner); - await repositoryManager.Initialize(); + repositoryManager.Initialize(); repositoryManager.Start(); var repositoryManagerListener = Substitute.For(); @@ -180,8 +179,8 @@ public async Task ShouldRefreshAndReturnCombinedStatusAndLockInformation1() result.HasValue.Should().BeTrue(); result.Value.AssertEqual(expectedGitStatus); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(); + repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Args.String); + repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); repositoryManagerListener.DidNotReceive().OnHeadChanged(); repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); @@ -243,8 +242,8 @@ public void ShouldRefreshAndReturnCombinedStatusAndLockInformation2() result.HasValue.Should().BeTrue(); result.Value.AssertNotEqual(expectedGitStatus); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(); + repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Args.String); + repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); repositoryManagerListener.DidNotReceive().OnHeadChanged(); repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); @@ -283,8 +282,8 @@ public void ShouldRefreshAndReturnWithEmptyGitLockResponse() result.HasValue.Should().BeTrue(); result.Value.AssertEqual(responseGitStatus); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(); + repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Args.String); + repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); repositoryManagerListener.DidNotReceive().OnHeadChanged(); repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged(); @@ -323,8 +322,8 @@ public void ShouldRefreshAndReturnWithNoGitLockResponse() result.HasValue.Should().BeTrue(); result.Value.AssertEqual(responseGitStatus); - repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(); - repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(); + repositoryManagerListener.DidNotReceive().OnActiveBranchChanged(Args.String); + repositoryManagerListener.DidNotReceive().OnActiveRemoteChanged(Arg.Any()); repositoryManagerListener.DidNotReceive().OnHeadChanged(); repositoryManagerListener.DidNotReceive().OnLocalBranchListChanged(); repositoryManagerListener.DidNotReceive().OnRemoteBranchListChanged();