void HandleCreatedRepo(ISimpleRepositoryModel newrepo) { var msg = string.Format(CultureInfo.CurrentUICulture, Constants.Notification_RepoCreated, newrepo.Name, newrepo.CloneUrl); msg += " " + string.Format(CultureInfo.CurrentUICulture, Constants.Notification_CreateNewProject, newrepo.LocalPath); ShowNotification(newrepo, msg); }
/// <summary> /// Gets a collection of Pull Requests. If you want to refresh existing data, pass a collection in /// </summary> /// <param name="repo"></param> /// <param name="collection"></param> /// <returns></returns> public ITrackingCollection <IPullRequestModel> GetPullRequests(ISimpleRepositoryModel repo, ITrackingCollection <IPullRequestModel> collection) { // Since the api to list pull requests returns all the data for each pr, cache each pr in its own entry // and also cache an index that contains all the keys for each pr. This way we can fetch prs in bulk // but also individually without duplicating information. We store things in a custom observable collection // that checks whether an item is being updated (coming from the live stream after being retrieved from cache) // and replaces it instead of appending, so items get refreshed in-place as they come in. var keyobs = GetUserFromCache() .Select(user => string.Format(CultureInfo.InvariantCulture, "{0}|{1}:{2}", CacheIndex.PRPrefix, user.Login, repo.Name)); var source = Observable.Defer(() => keyobs .SelectMany(key => hostCache.GetAndFetchLatestFromIndex(key, () => apiClient.GetPullRequestsForRepository(repo.CloneUrl.Owner, repo.CloneUrl.RepositoryName) .Select(PullRequestCacheItem.Create), item => { // this could blow up due to the collection being disposed somewhere else try { collection.RemoveItem(Create(item)); } catch (ObjectDisposedException) { } }, TimeSpan.Zero, TimeSpan.FromDays(7)) ) .Select(Create) ); collection.Listen(source); return(collection); }
async Task <IPullRequestModel> PushAndCreatePR(IRepositoryHost host, ISimpleRepositoryModel sourceRepository, ISimpleRepositoryModel targetRepository, IBranch sourceBranch, IBranch targetBranch, string title, string body) { var repo = await Task.Run(() => gitService.GetRepository(sourceRepository.LocalPath)); var remote = await gitClient.GetHttpRemote(repo, "origin"); await gitClient.Push(repo, sourceBranch.Name, remote.Name); if (!repo.Branches[sourceBranch.Name].IsTracking) { await gitClient.SetTrackingBranch(repo, sourceBranch.Name, remote.Name); } // delay things a bit to avoid a race between pushing a new branch and creating a PR on it if (!Splat.ModeDetector.Current.InUnitTestRunner().GetValueOrDefault()) { await Task.Delay(TimeSpan.FromSeconds(5)); } var ret = await host.ModelService.CreatePullRequest(sourceRepository, targetRepository, sourceBranch, targetBranch, title, body); usageTracker.IncrementUpstreamPullRequestCount(); return(ret); }
void ActiveRepoPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName == "CloneUrl") { ActiveRepo = sender as ISimpleRepositoryModel; } }
public PullRequestListViewModel( IRepositoryHost repositoryHost, ISimpleRepositoryModel repository, IPackageSettings settings) { this.repositoryHost = repositoryHost; this.repository = repository; this.settings = settings; this.listSettings = settings.UIState .GetOrCreateRepositoryState(repository.CloneUrl) .PullRequests; openPullRequestCommand = ReactiveCommand.Create(); openPullRequestCommand.Subscribe(_ => { VisualStudio.Services.DefaultExportProvider.GetExportedValue <IVisualStudioBrowser>().OpenUrl(repositoryHost.Address.WebUri); }); States = new List <PullRequestState> { new PullRequestState { IsOpen = true, Name = "Open" }, new PullRequestState { IsOpen = false, Name = "Closed" }, new PullRequestState { Name = "All" } }; trackingAuthors = new TrackingCollection <IAccount>(Observable.Empty <IAccount>(), OrderedComparer <IAccount> .OrderByDescending(x => x.Login).Compare); trackingAssignees = new TrackingCollection <IAccount>(Observable.Empty <IAccount>(), OrderedComparer <IAccount> .OrderByDescending(x => x.Login).Compare); trackingAuthors.Subscribe(); trackingAssignees.Subscribe(); Authors = trackingAuthors.CreateListenerCollection(EmptyUser, this.WhenAnyValue(x => x.SelectedAuthor)); Assignees = trackingAssignees.CreateListenerCollection(EmptyUser, this.WhenAnyValue(x => x.SelectedAssignee)); PullRequests = new TrackingCollection <IPullRequestModel>(); pullRequests.Comparer = OrderedComparer <IPullRequestModel> .OrderByDescending(x => x.UpdatedAt).Compare; pullRequests.NewerComparer = OrderedComparer <IPullRequestModel> .OrderByDescending(x => x.UpdatedAt).Compare; this.WhenAny(x => x.SelectedState, x => x.Value) .Where(x => PullRequests != null) .Subscribe(s => UpdateFilter(s, SelectedAssignee, SelectedAuthor)); this.WhenAny(x => x.SelectedAssignee, x => x.Value) .Where(x => PullRequests != null && x != EmptyUser && IsLoaded) .Subscribe(a => UpdateFilter(SelectedState, a, SelectedAuthor)); this.WhenAny(x => x.SelectedAuthor, x => x.Value) .Where(x => PullRequests != null && x != EmptyUser && IsLoaded) .Subscribe(a => UpdateFilter(SelectedState, SelectedAssignee, a)); SelectedState = States.FirstOrDefault(x => x.Name == listSettings.SelectedState) ?? States[0]; }
void UpdateRepo(ISimpleRepositoryModel repo) { var changed = ActiveRepo != repo; ActiveRepo = repo; RepoChanged(changed); Invalidate(); }
public static bool MightContainSolution(this ISimpleRepositoryModel repository) { var dir = new DirectoryInfo(repository.LocalPath); return(dir.EnumerateFileSystemInfos("*", SearchOption.TopDirectoryOnly) .Any(x => ((x.Attributes.HasFlag(FileAttributes.Directory) || x.Attributes.HasFlag(FileAttributes.Normal)) && !x.Name.StartsWith(".", StringComparison.Ordinal) && !x.Name.StartsWith("readme", StringComparison.OrdinalIgnoreCase)))); }
public BranchModel(string name, ISimpleRepositoryModel repo) { Extensions.Guard.ArgumentNotEmptyString(name, nameof(name)); Extensions.Guard.ArgumentNotNull(repo, nameof(repo)); Name = DisplayName = name; Repository = repo; Id = String.Format(CultureInfo.InvariantCulture, "{0}/{1}", Repository.Owner, Name); }
public BranchModel(Octokit.Branch branch, ISimpleRepositoryModel repo) { Extensions.Guard.ArgumentNotNull(branch, nameof(branch)); Extensions.Guard.ArgumentNotNull(repo, nameof(repo)); Name = DisplayName = branch.Name; Repository = repo; Id = String.Format(CultureInfo.InvariantCulture, "{0}/{1}", Repository.Owner, Name); }
public IObservable <IBranch> GetBranches(ISimpleRepositoryModel repo) { var keyobs = GetUserFromCache() .Select(user => string.Format(CultureInfo.InvariantCulture, "{0}|{1}|branch", user.Login, repo.Name)); return(Observable.Defer(() => keyobs .SelectMany(key => apiClient.GetBranches(repo.CloneUrl.Owner, repo.CloneUrl.RepositoryName))) .Select(x => new BranchModel(x, repo))); }
public PullRequestListViewModel(IRepositoryHost repositoryHost, ISimpleRepositoryModel repository) { this.repositoryHost = repositoryHost; this.repository = repository; openPullRequestCommand = ReactiveCommand.Create(); openPullRequestCommand.Subscribe(_ => { VisualStudio.Services.DefaultExportProvider.GetExportedValue <IVisualStudioBrowser>().OpenUrl(repositoryHost.Address.WebUri); }); States = new List <PullRequestState> { new PullRequestState { IsOpen = true, Name = "Open" }, new PullRequestState { IsOpen = false, Name = "Closed" }, new PullRequestState { Name = "All" } }; SelectedState = States[0]; this.WhenAny(x => x.SelectedState, x => x.Value) .Where(x => PullRequests != null) .Subscribe(s => UpdateFilter(s, SelectedAssignee, SelectedAuthor)); this.WhenAny(x => x.SelectedAssignee, x => x.Value) .Where(x => PullRequests != null) .Subscribe(a => UpdateFilter(SelectedState, a, SelectedAuthor)); this.WhenAny(x => x.SelectedAuthor, x => x.Value) .Where(x => PullRequests != null) .Subscribe(a => UpdateFilter(SelectedState, SelectedAssignee, a)); trackingAuthors = new TrackingCollection <IAccount>(Observable.Empty <IAccount>(), OrderedComparer <IAccount> .OrderByDescending(x => x.Login).Compare); trackingAssignees = new TrackingCollection <IAccount>(Observable.Empty <IAccount>(), OrderedComparer <IAccount> .OrderByDescending(x => x.Login).Compare); trackingAuthors.Subscribe(); trackingAssignees.Subscribe(); Authors = trackingAuthors.CreateListenerCollection(new List <IAccount> { EmptyUser }); Assignees = trackingAssignees.CreateListenerCollection(new List <IAccount> { EmptyUser }); PullRequests = new TrackingCollection <IPullRequestModel>(); pullRequests.Comparer = OrderedComparer <IPullRequestModel> .OrderByDescending(x => x.UpdatedAt).Compare; pullRequests.Filter = (pr, i, l) => pr.IsOpen; pullRequests.NewerComparer = OrderedComparer <IPullRequestModel> .OrderByDescending(x => x.UpdatedAt).Compare; }
public BranchModel(LibGit2Sharp.Branch branch, ISimpleRepositoryModel repo) { Extensions.Guard.ArgumentNotNull(branch, nameof(branch)); Extensions.Guard.ArgumentNotNull(repo, nameof(repo)); Name = DisplayName = branch.FriendlyName; Repository = branch.IsRemote ? new SimpleRepositoryModel(branch.Remote.Url) : repo; IsTracking = branch.IsTracking; Id = String.Format(CultureInfo.InvariantCulture, "{0}/{1}", Repository.Owner, Name); }
void HandleClonedRepo(ISimpleRepositoryModel newrepo) { var msg = string.Format(CultureInfo.CurrentUICulture, Constants.Notification_RepoCloned, newrepo.Name, newrepo.CloneUrl); if (newrepo.HasCommits() && newrepo.MightContainSolution()) { msg += " " + string.Format(CultureInfo.CurrentUICulture, Constants.Notification_OpenProject, newrepo.LocalPath); } else { msg += " " + string.Format(CultureInfo.CurrentUICulture, Constants.Notification_CreateNewProject, newrepo.LocalPath); } ShowNotification(newrepo, msg); }
public IObservable <IPullRequestModel> CreatePullRequest(IRepositoryHost host, ISimpleRepositoryModel sourceRepository, ISimpleRepositoryModel targetRepository, IBranch sourceBranch, IBranch targetBranch, string title, string body ) { Extensions.Guard.ArgumentNotNull(host, nameof(host)); Extensions.Guard.ArgumentNotNull(sourceRepository, nameof(sourceRepository)); Extensions.Guard.ArgumentNotNull(targetRepository, nameof(targetRepository)); Extensions.Guard.ArgumentNotNull(sourceBranch, nameof(sourceBranch)); Extensions.Guard.ArgumentNotNull(targetBranch, nameof(targetBranch)); Extensions.Guard.ArgumentNotNull(title, nameof(title)); Extensions.Guard.ArgumentNotNull(body, nameof(body)); return(PushAndCreatePR(host, sourceRepository, targetRepository, sourceBranch, targetBranch, title, body).ToObservable()); }
void RefreshRepo() { ActiveRepo = ServiceProvider.GetExportedValue<ITeamExplorerServiceHolder>().ActiveRepo; if (ActiveRepo == null) { var vsservices = ServiceProvider.GetExportedValue<IVSServices>(); string path = vsservices?.GetActiveRepoPath() ?? String.Empty; try { ActiveRepo = !String.IsNullOrEmpty(path) ? new SimpleRepositoryModel(path) : null; } catch (Exception ex) { VsOutputLogger.WriteLine(string.Format(CultureInfo.CurrentCulture, "{0}: Error loading the repository from '{1}'. {2}", GetType(), path, ex)); } } }
void RefreshRepo() { ActiveRepo = ServiceProvider.GetExportedValue <ITeamExplorerServiceHolder>().ActiveRepo; if (ActiveRepo == null) { var vsservices = ServiceProvider.GetExportedValue <IVSServices>(); string path = vsservices?.GetActiveRepoPath() ?? String.Empty; try { ActiveRepo = !String.IsNullOrEmpty(path) ? new SimpleRepositoryModel(path) : null; } catch (Exception ex) { VsOutputLogger.WriteLine(string.Format(CultureInfo.CurrentCulture, "{0}: Error loading the repository from '{1}'. {2}", GetType(), path, ex)); } } }
public IObservable <string> GetPullRequestTemplate(ISimpleRepositoryModel repository) { Extensions.Guard.ArgumentNotNull(repository, nameof(repository)); return(Observable.Defer(() => { var paths = TemplatePaths.Select(x => Path.Combine(repository.LocalPath, x)); foreach (var path in paths) { if (os.File.Exists(path)) { try { return Observable.Return(os.File.ReadAllText(path, Encoding.UTF8)); } catch { } } } return Observable.Empty <string>(); })); }
void ShowNotification(ISimpleRepositoryModel newrepo, string msg) { var teServices = ServiceProvider.GetExportedValue <ITeamExplorerServices>(); teServices.ClearNotifications(); teServices.ShowMessage( msg, new RelayCommand(o => { var str = o.ToString(); /* the prefix is the action to perform: * u: launch browser with url * c: launch create new project dialog * o: launch open existing project dialog */ var prefix = str.Substring(0, 2); if (prefix == "u:") { OpenInBrowser(ServiceProvider.GetExportedValue <IVisualStudioBrowser>(), new Uri(str.Substring(2))); } else if (prefix == "o:") { if (ErrorHandler.Succeeded(ServiceProvider.GetSolution().OpenSolutionViaDlg(str.Substring(2), 1))) { ServiceProvider.TryGetService <ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); } } else if (prefix == "c:") { var vsGitServices = ServiceProvider.GetExportedValue <IVSGitServices>(); vsGitServices.SetDefaultProjectPath(newrepo.LocalPath); if (ErrorHandler.Succeeded(ServiceProvider.GetSolution().CreateNewProjectViaDlg(null, null, 0))) { ServiceProvider.TryGetService <ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); } } }) ); #if DEBUG VsOutputLogger.WriteLine(String.Format(CultureInfo.InvariantCulture, "{0} Notification", DateTime.Now)); #endif }
async void UpdateRepositoryList(object sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Add) { // if we're cloning or creating, only one repo will be added to the list // so we can handle just one new entry separately if (isCloning || isCreating) { var newrepo = e.NewItems.Cast <ISimpleRepositoryModel>().First(); SelectedRepository = newrepo; if (isCreating) { HandleCreatedRepo(newrepo); } else { HandleClonedRepo(newrepo); } isCreating = isCloning = false; var repo = await ApiFactory.Create(newrepo.CloneUrl).GetRepository(); newrepo.SetIcon(repo.Private, repo.Fork); } // looks like it's just a refresh with new stuff on the list, update the icons else { e.NewItems .Cast <ISimpleRepositoryModel>() .ForEach(async r => { if (Equals(Holder.ActiveRepo, r)) { SelectedRepository = r; } var repo = await ApiFactory.Create(r.CloneUrl).GetRepository(); r.SetIcon(repo.Private, repo.Fork); }); } } }
public bool OpenRepository() { var old = Repositories.FirstOrDefault(x => x.Equals(Holder.ActiveRepo)); // open the solution selection dialog when the user wants to switch to a different repo // since there's no other way of changing the source control context in VS if (!Equals(SelectedRepository, old)) { if (ErrorHandler.Succeeded(ServiceProvider.GetSolution().OpenSolutionViaDlg(SelectedRepository.LocalPath, 1))) { ServiceProvider.TryGetService <ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); return(true); } else { SelectedRepository = old; return(false); } } return(false); }
void CheckAndUpdate(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName != "ActiveRepositories") { return; } var service = GitService; if (service == null) { return; } var repo = service.ActiveRepositories.FirstOrDefault()?.ToModel(); if (repo != ActiveRepo) { // so annoying that this is on the wrong thread syncContext.Post(r => ActiveRepo = r as ISimpleRepositoryModel, repo); } }
public void CreatePullRequestAllArgsMandatory() { var serviceProvider = Substitutes.ServiceProvider; var service = new PullRequestService(Substitute.For <IGitClient>(), serviceProvider.GetGitService(), serviceProvider.GetOperatingSystem(), Substitute.For <IUsageTracker>()); IRepositoryHost host = null; ISimpleRepositoryModel sourceRepo = null; ISimpleRepositoryModel targetRepo = null; string title = null; string body = null; IBranch source = null; IBranch target = null; Assert.Throws <ArgumentNullException>(() => service.CreatePullRequest(host, sourceRepo, targetRepo, source, target, title, body)); host = serviceProvider.GetRepositoryHosts().GitHubHost; Assert.Throws <ArgumentNullException>(() => service.CreatePullRequest(host, sourceRepo, targetRepo, source, target, title, body)); sourceRepo = new SimpleRepositoryModel("name", new GitHub.Primitives.UriString("http://github.com/github/stuff")); Assert.Throws <ArgumentNullException>(() => service.CreatePullRequest(host, sourceRepo, targetRepo, source, target, title, body)); targetRepo = new SimpleRepositoryModel("name", new GitHub.Primitives.UriString("http://github.com/github/stuff")); Assert.Throws <ArgumentNullException>(() => service.CreatePullRequest(host, sourceRepo, targetRepo, source, target, title, body)); title = "a title"; Assert.Throws <ArgumentNullException>(() => service.CreatePullRequest(host, sourceRepo, targetRepo, source, target, title, body)); body = "a body"; Assert.Throws <ArgumentNullException>(() => service.CreatePullRequest(host, sourceRepo, targetRepo, source, target, title, body)); source = new BranchModel("source", sourceRepo); Assert.Throws <ArgumentNullException>(() => service.CreatePullRequest(host, sourceRepo, targetRepo, source, target, title, body)); target = new BranchModel("target", targetRepo); var pr = service.CreatePullRequest(host, sourceRepo, targetRepo, source, target, title, body); Assert.NotNull(pr); }
async void UIContextChanged(bool active, bool refresh) { Debug.Assert(ServiceProvider != null, "UIContextChanged called before service provider is set"); if (ServiceProvider == null) { return; } if (active) { GitService = GitService ?? ServiceProvider.GetService <IGitExt>(); if (ActiveRepo == null || refresh) { ActiveRepo = await System.Threading.Tasks.Task.Run(() => { var repos = GitService?.ActiveRepositories; // Looks like this might return null after a while, for some unknown reason // if it does, let's refresh the GitService instance in case something got wonky // and try again. See issue #23 if (repos == null) { VsOutputLogger.WriteLine(string.Format(CultureInfo.CurrentCulture, "Error 2001: ActiveRepositories is null. GitService: '{0}'", GitService)); GitService = ServiceProvider?.GetService <IGitExt>(); repos = GitService?.ActiveRepositories; if (repos == null) { VsOutputLogger.WriteLine(string.Format(CultureInfo.CurrentCulture, "Error 2002: ActiveRepositories is null. GitService: '{0}'", GitService)); } } return(repos?.FirstOrDefault()?.ToModel()); }); } } else { ActiveRepo = null; } }
public void GenerateUrl(int testid, bool createRootedPath, string baseUrl, string sha, string path, int startLine, int endLine, string expected) { SetupRepository(sha); var basePath = Directory.CreateSubdirectory("generate-url-test1-" + testid); if (createRootedPath && path != null) { path = System.IO.Path.Combine(basePath.FullName, path); } ISimpleRepositoryModel model = null; if (!String.IsNullOrEmpty(baseUrl)) { model = new SimpleRepositoryModel("bar", new UriString(baseUrl), basePath.FullName); } else { model = new SimpleRepositoryModel(basePath.FullName); } var result = model.GenerateUrl(path, startLine, endLine); Assert.Equal(expected, result?.ToString()); }
public bool OpenRepository() { var old = Repositories.FirstOrDefault(x => x.Equals(Holder.ActiveRepo)); // open the solution selection dialog when the user wants to switch to a different repo // since there's no other way of changing the source control context in VS if (!Equals(SelectedRepository, old)) { if (ErrorHandler.Succeeded(ServiceProvider.GetSolution().OpenSolutionViaDlg(SelectedRepository.LocalPath, 1))) { ServiceProvider.TryGetService<ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); return true; } else { SelectedRepository = old; return false; } } return false; }
void UpdateRepo(ISimpleRepositoryModel repo) { ActiveRepo = repo; RepoChanged(); Invalidate(); }
void HandleClonedRepo(ISimpleRepositoryModel newrepo) { var msg = string.Format(CultureInfo.CurrentUICulture, Constants.Notification_RepoCloned, newrepo.Name, newrepo.CloneUrl); if (newrepo.HasCommits() && newrepo.MightContainSolution()) msg += " " + string.Format(CultureInfo.CurrentUICulture, Constants.Notification_OpenProject, newrepo.LocalPath); else msg += " " + string.Format(CultureInfo.CurrentUICulture, Constants.Notification_CreateNewProject, newrepo.LocalPath); ShowNotification(newrepo, msg); }
void ShowNotification(ISimpleRepositoryModel newrepo, string msg) { var vsservices = ServiceProvider.GetExportedValue<IVSServices>(); vsservices.ClearNotifications(); vsservices.ShowMessage( msg, new RelayCommand(o => { var str = o.ToString(); /* the prefix is the action to perform: * u: launch browser with url * c: launch create new project dialog * o: launch open existing project dialog */ var prefix = str.Substring(0, 2); if (prefix == "u:") OpenInBrowser(ServiceProvider.TryGetService<IVisualStudioBrowser>(), new Uri(str.Substring(2))); else if (prefix == "o:") { if (ErrorHandler.Succeeded(ServiceProvider.GetSolution().OpenSolutionViaDlg(str.Substring(2), 1))) ServiceProvider.TryGetService<ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); } else if (prefix == "c:") { vsservices.SetDefaultProjectPath(newrepo.LocalPath); if (ErrorHandler.Succeeded(ServiceProvider.GetSolution().CreateNewProjectViaDlg(null, null, 0))) ServiceProvider.TryGetService<ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); } }) ); #if DEBUG VsOutputLogger.WriteLine(String.Format(CultureInfo.InvariantCulture, "{0} Notification", DateTime.Now)); #endif }
async Task <CodeContainer> RunAcquisition(IProgress <ServiceProgressData> downloadProgress, CancellationToken cancellationToken, ISimpleRepositoryModel repository) { CloneRequest request = null; try { var uiProvider = await Task.Run(() => Package.GetGlobalService(typeof(IUIProvider)) as IUIProvider); var cm = uiProvider.TryGetService <IConnectionManager>(); var gitRepositories = await GetGitRepositoriesExt(uiProvider); request = ShowCloneDialog(uiProvider, gitRepositories, repository); } catch { // TODO: log } if (request == null) { return(null); } var path = Path.Combine(request.BasePath, request.Repository.Name); var uri = request.Repository.CloneUrl.ToRepositoryUrl(); return(new CodeContainer( localProperties: new CodeContainerLocalProperties(path, CodeContainerType.Folder, new CodeContainerSourceControlProperties(request.Repository.Name, path, GitSccProvider)), remote: new RemoteCodeContainer(request.Repository.Name, GitSccProvider, uri, new Uri(uri.ToString().TrimSuffix(".git")), DateTimeOffset.UtcNow), isFavorite: false, lastAccessed: DateTimeOffset.UtcNow)); }
public static bool HasCommits(this ISimpleRepositoryModel repository) { var repo = GitService.GitServiceHelper.GetRepository(repository.LocalPath); return(repo?.Commits.Any() ?? false); }
void ActiveRepoPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName == "CloneUrl") ActiveRepo = sender as ISimpleRepositoryModel; }
async void UpdateRepositoryList(object sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Add) { // if we're cloning or creating, only one repo will be added to the list // so we can handle just one new entry separately if (isCloning || isCreating) { var newrepo = e.NewItems.Cast<ISimpleRepositoryModel>().First(); SelectedRepository = newrepo; if (isCreating) { var vsservices = ServiceProvider.GetExportedValue<IVSServices>(); vsservices.ClearNotifications(); vsservices.ShowMessage(string.Format(CultureInfo.CurrentUICulture, "[{0}]({1}) has been successfully created.", newrepo.Name, newrepo.CloneUrl)); } // if we've cloned a repo but the user didn't open a project in it, // then update the newly-cloned repo icon because we're not going to // switch to the TE home page if ((isCloning && !OpenRepository()) || isCreating) { var repo = await ApiFactory.Create(newrepo.CloneUrl).GetRepository(); newrepo.SetIcon(repo.Private, repo.Fork); } } // looks like it's just a refresh with new stuff on the list, update the icons else { e.NewItems .Cast<ISimpleRepositoryModel>() .ForEach(async r => { if (String.Equals(Holder.ActiveRepo?.RepositoryPath, r.LocalPath, StringComparison.CurrentCultureIgnoreCase)) SelectedRepository = r; var repo = await ApiFactory.Create(r.CloneUrl).GetRepository(); r.SetIcon(repo.Private, repo.Fork); }); } } }
public PullRequestCreationViewModel(IRepositoryHost repositoryHost, ISimpleRepositoryModel activeRepo, IPullRequestService service, INotificationService notifications) { Extensions.Guard.ArgumentNotNull(repositoryHost, nameof(repositoryHost)); Extensions.Guard.ArgumentNotNull(activeRepo, nameof(activeRepo)); Extensions.Guard.ArgumentNotNull(service, nameof(service)); Extensions.Guard.ArgumentNotNull(notifications, nameof(notifications)); this.repositoryHost = repositoryHost; var obs = repositoryHost.ApiClient.GetRepository(activeRepo.Owner, activeRepo.Name) .Select(r => new RepositoryModel(r)) .PublishLast(); disposables.Add(obs.Connect()); githubObs = obs; githubRepository = githubObs.ToProperty(this, x => x.GitHubRepository); this.WhenAnyValue(x => x.GitHubRepository) .WhereNotNull() .Subscribe(r => { TargetBranch = r.IsFork ? r.Parent.DefaultBranch : r.DefaultBranch; }); SourceBranch = activeRepo.CurrentBranch; service.GetPullRequestTemplate(activeRepo) .Subscribe(x => Description = x ?? String.Empty, () => Description = Description ?? String.Empty); this.WhenAnyValue(x => x.Branches) .WhereNotNull() .Where(_ => TargetBranch != null) .Subscribe(x => { if (!x.Any(t => t.Equals(TargetBranch))) { TargetBranch = GitHubRepository.IsFork ? GitHubRepository.Parent.DefaultBranch : GitHubRepository.DefaultBranch; } }); SetupValidators(); var whenAnyValidationResultChanges = this.WhenAny( x => x.TitleValidator.ValidationResult, x => x.BranchValidator.ValidationResult, x => x.IsBusy, (x, y, z) => (x.Value?.IsValid ?? false) && (y.Value?.IsValid ?? false) && !z.Value); this.WhenAny(x => x.BranchValidator.ValidationResult, x => x.GetValue()) .WhereNotNull() .Where(x => !x.IsValid && x.DisplayValidationError) .Subscribe(x => notifications.ShowError(BranchValidator.ValidationResult.Message)); createPullRequest = ReactiveCommand.CreateAsyncObservable(whenAnyValidationResultChanges, _ => service .CreatePullRequest(repositoryHost, activeRepo, TargetBranch.Repository, SourceBranch, TargetBranch, PRTitle, Description ?? String.Empty) .Catch <IPullRequestModel, Exception>(ex => { log.Error(ex); //TODO:Will need a uniform solution to HTTP exception message handling var apiException = ex as ApiValidationException; var error = apiException?.ApiError?.Errors?.FirstOrDefault(); notifications.ShowError(error?.Message ?? ex.Message); return(Observable.Empty <IPullRequestModel>()); })) .OnExecuteCompleted(pr => { notifications.ShowMessage(String.Format(CultureInfo.CurrentCulture, Resources.PRCreatedUpstream, TargetBranch.Id, TargetBranch.Repository.CloneUrl.ToRepositoryUrl().Append("pull/" + pr.Number))); }); isExecuting = CreatePullRequest.IsExecuting.ToProperty(this, x => x.IsExecuting); this.WhenAnyValue(x => x.Initialized, x => x.GitHubRepository, x => x.Description, x => x.IsExecuting) .Select(x => !(x.Item1 && x.Item2 != null && x.Item3 != null && !x.Item4)) .Subscribe(x => IsBusy = x); }
void UIContextChanged(object sender, UIContextChangedEventArgs e) { ActiveRepo = null; UIContextChanged(e.Activated, false); }
public static string CurrentSha(this ISimpleRepositoryModel repository) { var repo = GitService.GitServiceHelper.GetRepo(repository.LocalPath); return(repo.Commits.FirstOrDefault()?.Sha); }
void CheckAndUpdate(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName != "ActiveRepositories") return; var service = GitService; if (service == null) return; var repo = service.ActiveRepositories.FirstOrDefault()?.ToModel(); if (repo != ActiveRepo) // so annoying that this is on the wrong thread syncContext.Post(r => ActiveRepo = r as ISimpleRepositoryModel, repo); }
async void UIContextChanged(bool active, bool refresh) { Debug.Assert(ServiceProvider != null, "UIContextChanged called before service provider is set"); if (ServiceProvider == null) return; if (active) { GitService = GitService ?? ServiceProvider.GetService<IGitExt>(); if (ActiveRepo == null || refresh) ActiveRepo = await System.Threading.Tasks.Task.Run(() => { var repos = GitService?.ActiveRepositories; // Looks like this might return null after a while, for some unknown reason // if it does, let's refresh the GitService instance in case something got wonky // and try again. See issue #23 if (repos == null) { VsOutputLogger.WriteLine(string.Format(CultureInfo.CurrentCulture, "Error 2001: ActiveRepositories is null. GitService: '{0}'", GitService)); GitService = ServiceProvider?.GetService<IGitExt>(); repos = GitService?.ActiveRepositories; if (repos == null) VsOutputLogger.WriteLine(string.Format(CultureInfo.CurrentCulture, "Error 2002: ActiveRepositories is null. GitService: '{0}'", GitService)); } return repos?.FirstOrDefault()?.ToModel(); }); } else ActiveRepo = null; }
void OnPropertyChange(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "IsVisible" && IsVisible && View == null) { View = new GitHubConnectContent { DataContext = this } } ; } async void UpdateRepositoryList(object sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Add) { // if we're cloning or creating, only one repo will be added to the list // so we can handle just one new entry separately if (isCloning || isCreating) { var newrepo = e.NewItems.Cast <ISimpleRepositoryModel>().First(); SelectedRepository = newrepo; if (isCreating) { HandleCreatedRepo(newrepo); } else { HandleClonedRepo(newrepo); } var repo = await ApiFactory.Create(newrepo.CloneUrl).GetRepository(); newrepo.SetIcon(repo.Private, repo.Fork); } // looks like it's just a refresh with new stuff on the list, update the icons else { e.NewItems .Cast <ISimpleRepositoryModel>() .ForEach(async r => { if (Equals(Holder.ActiveRepo, r)) { SelectedRepository = r; } var repo = await ApiFactory.Create(r.CloneUrl).GetRepository(); r.SetIcon(repo.Private, repo.Fork); }); } } } void HandleCreatedRepo(ISimpleRepositoryModel newrepo) { var msg = string.Format(CultureInfo.CurrentUICulture, Constants.Notification_RepoCreated, newrepo.Name, newrepo.CloneUrl); msg += " " + string.Format(CultureInfo.CurrentUICulture, Constants.Notification_CreateNewProject, newrepo.LocalPath); ShowNotification(newrepo, msg); } void HandleClonedRepo(ISimpleRepositoryModel newrepo) { var msg = string.Format(CultureInfo.CurrentUICulture, Constants.Notification_RepoCloned, newrepo.Name, newrepo.CloneUrl); if (newrepo.HasCommits() && newrepo.MightContainSolution()) { msg += " " + string.Format(CultureInfo.CurrentUICulture, Constants.Notification_OpenProject, newrepo.LocalPath); } else { msg += " " + string.Format(CultureInfo.CurrentUICulture, Constants.Notification_CreateNewProject, newrepo.LocalPath); } ShowNotification(newrepo, msg); } void ShowNotification(ISimpleRepositoryModel newrepo, string msg) { var vsservices = ServiceProvider.GetExportedValue <IVSServices>(); vsservices.ClearNotifications(); vsservices.ShowMessage( msg, new RelayCommand(o => { var str = o.ToString(); /* the prefix is the action to perform: * u: launch browser with url * c: launch create new project dialog * o: launch open existing project dialog */ var prefix = str.Substring(0, 2); if (prefix == "u:") { OpenInBrowser(ServiceProvider.TryGetService <IVisualStudioBrowser>(), new Uri(str.Substring(2))); } else if (prefix == "o:") { if (ErrorHandler.Succeeded(ServiceProvider.GetSolution().OpenSolutionViaDlg(str.Substring(2), 1))) { ServiceProvider.TryGetService <ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); } } else if (prefix == "c:") { vsservices.SetDefaultProjectPath(newrepo.LocalPath); if (ErrorHandler.Succeeded(ServiceProvider.GetSolution().CreateNewProjectViaDlg(null, null, 0))) { ServiceProvider.TryGetService <ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); } } }) ); #if DEBUG VsOutputLogger.WriteLine(String.Format(CultureInfo.InvariantCulture, "{0} Notification", DateTime.Now)); #endif } void RefreshRepositories() { connectionManager.RefreshRepositories(); RaisePropertyChanged("Repositories"); // trigger a re-check of the visibility of the listview based on item count }
async void UpdateRepositoryList(object sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Add) { // if we're cloning or creating, only one repo will be added to the list // so we can handle just one new entry separately if (isCloning || isCreating) { var newrepo = e.NewItems.Cast<ISimpleRepositoryModel>().First(); SelectedRepository = newrepo; if (isCreating) HandleCreatedRepo(newrepo); else HandleClonedRepo(newrepo); isCreating = isCloning = false; var repo = await ApiFactory.Create(newrepo.CloneUrl).GetRepository(); newrepo.SetIcon(repo.Private, repo.Fork); } // looks like it's just a refresh with new stuff on the list, update the icons else { e.NewItems .Cast<ISimpleRepositoryModel>() .ForEach(async r => { if (Equals(Holder.ActiveRepo, r)) SelectedRepository = r; var repo = await ApiFactory.Create(r.CloneUrl).GetRepository(); r.SetIcon(repo.Private, repo.Fork); }); } } }
public ITrackingCollection<IPullRequestModel> GetPullRequests(ISimpleRepositoryModel repo) { // Since the api to list pull requests returns all the data for each pr, cache each pr in its own entry // and also cache an index that contains all the keys for each pr. This way we can fetch prs in bulk // but also individually without duplicating information. We store things in a custom observable collection // that checks whether an item is being updated (coming from the live stream after being retrieved from cache) // and replaces it instead of appending, so items get refreshed in-place as they come in. var keyobs = GetUserFromCache() .Select(user => string.Format(CultureInfo.InvariantCulture, "{0}|{1}|pr", user.Login, repo.Name)); var col = new TrackingCollection<IPullRequestModel>(); var source = Observable.Defer(() => keyobs .SelectMany(key => hostCache.GetAndFetchLatestFromIndex(key, () => apiClient.GetPullRequestsForRepository(repo.CloneUrl.Owner, repo.CloneUrl.RepositoryName) .Select(PullRequestCacheItem.Create), item => col.RemoveItem(Create(item)), TimeSpan.FromMinutes(5), TimeSpan.FromDays(1)) ) .Select(Create) ); col.Listen(source); return col; }
public static IObservable <IConnection> LookupConnection(this IConnectionManager cm, ISimpleRepositoryModel repository) { return(Observable.Return(repository?.CloneUrl != null ? cm.Connections.FirstOrDefault(c => c.HostAddress.Equals(HostAddress.Create(repository.CloneUrl))) : null)); }