void UpdateRepo(ILocalRepositoryModel repo) { var changed = ActiveRepo != repo; ActiveRepo = repo; RepoChanged(changed); Invalidate(); }
/// <summary> /// Initializes a new instance of the <see cref="PullRequestDetailViewModel"/> class. /// </summary> /// <param name="repositoryHost">The repository host.</param> /// <param name="teservice">The team explorer service.</param> /// <param name="pullRequestsService">The pull requests service.</param> /// <param name="avatarProvider">The avatar provider.</param> public PullRequestDetailViewModel( ILocalRepositoryModel repository, IModelService modelService, IPullRequestService pullRequestsService, IPackageSettings settings) { this.repository = repository; this.modelService = modelService; this.pullRequestsService = pullRequestsService; Checkout = ReactiveCommand.CreateAsyncObservable( this.WhenAnyValue(x => x.CheckoutState) .Cast<CheckoutCommandState>() .Select(x => x != null && x.DisabledMessage == null), DoCheckout); Checkout.ThrownExceptions.Subscribe(x => OperationError = x.Message); Pull = ReactiveCommand.CreateAsyncObservable( this.WhenAnyValue(x => x.UpdateState) .Cast<UpdateCommandState>() .Select(x => x != null && x.PullDisabledMessage == null), DoPull); Pull.ThrownExceptions.Subscribe(x => OperationError = x.Message); Push = ReactiveCommand.CreateAsyncObservable( this.WhenAnyValue(x => x.UpdateState) .Cast<UpdateCommandState>() .Select(x => x != null && x.PushDisabledMessage == null), DoPush); Push.ThrownExceptions.Subscribe(x => OperationError = x.Message); OpenOnGitHub = ReactiveCommand.Create(); ChangedFilesViewType = (settings.UIState?.PullRequestDetailState?.ShowTree ?? true) ? ChangedFilesViewType.TreeView : ChangedFilesViewType.ListView; ToggleChangedFilesView = ReactiveCommand.Create(); ToggleChangedFilesView.Subscribe(_ => { ChangedFilesViewType = ChangedFilesViewType == ChangedFilesViewType.TreeView ? ChangedFilesViewType.ListView : ChangedFilesViewType.TreeView; settings.UIState.PullRequestDetailState.ShowTree = ChangedFilesViewType == ChangedFilesViewType.TreeView; settings.Save(); }); OpenChangedFileAction = (settings.UIState?.PullRequestDetailState?.DiffOnOpen ?? true) ? OpenChangedFileAction.Diff : OpenChangedFileAction.Open; ToggleOpenChangedFileAction = ReactiveCommand.Create(); ToggleOpenChangedFileAction.Subscribe(_ => { OpenChangedFileAction = OpenChangedFileAction == OpenChangedFileAction.Diff ? OpenChangedFileAction.Open : OpenChangedFileAction.Diff; settings.UIState.PullRequestDetailState.DiffOnOpen = OpenChangedFileAction == OpenChangedFileAction.Diff; settings.Save(); }); OpenFile = ReactiveCommand.Create(); DiffFile = ReactiveCommand.Create(); }
public PullRequestListViewModel( IRepositoryHost repositoryHost, ILocalRepositoryModel repository, IPackageSettings settings) { this.repositoryHost = repositoryHost; this.repository = repository; this.settings = settings; Title = Resources.PullRequestsNavigationItemText; 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 BuildAccounts(IReadOnlyList <IAccount> accessibleAccounts, ILocalRepositoryModel currentRepository, IList <IRemoteRepositoryModel> forks, List <IRemoteRepositoryModel> parents) { log.Verbose("BuildAccounts: {AccessibleAccounts} accessibleAccounts, {Forks} forks, {Parents} parents", accessibleAccounts.Count, forks.Count, parents.Count); var existingForksAndParents = forks.Union(parents).ToDictionary(model => model.Owner); var readOnlyList = accessibleAccounts .Where(account => account.Login != currentRepository.Owner) .Select(account => new { Account = account, Fork = existingForksAndParents.ContainsKey(account.Login) ? existingForksAndParents[account.Login] : null }) .ToArray(); Accounts = readOnlyList.Where(arg => arg.Fork == null).Select(arg => arg.Account).ToList(); ExistingForks = readOnlyList.Where(arg => arg.Fork != null).Select(arg => arg.Fork).ToList(); }
public IObservable <Tuple <string, int> > GetPullRequestForCurrentBranch(ILocalRepositoryModel repository) { return(Observable.Defer(async() => { var repo = gitService.GetRepository(repository.LocalPath); var configKey = string.Format( CultureInfo.InvariantCulture, "branch.{0}.{1}", repo.Head.FriendlyName, SettingGHfVSPullRequest); var value = await gitClient.GetConfig <string>(repo, configKey); return Observable.Return(ParseGHfVSConfigKeyValue(value)); })); }
public IObservable <TreeChanges> GetTreeChanges(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest) { return(Observable.Defer(async() => { // TreeChanges doesn't keep a reference to Repository using (var repo = gitService.GetRepository(repository.LocalPath)) { var remote = await gitClient.GetHttpRemote(repo, "origin"); await gitClient.Fetch(repo, remote.Name); var changes = await gitClient.Compare(repo, pullRequest.BaseRefSha, pullRequest.HeadRefSha, detectRenames: true); return Observable.Return(changes); } })); }
/// <inheritdoc/> public async Task InitializeAsync(ILocalRepositoryModel repository, IConnection connection) { try { LocalRepository = repository; SelectedState = States.FirstOrDefault(); AuthorFilter = new UserFilterViewModel(LoadAuthors); IsLoading = true; var parent = await repositoryService.FindParent( HostAddress.Create(repository.CloneUrl), repository.Owner, repository.Name); if (parent == null) { RemoteRepository = repository; } else { // TODO: Handle forks with different names. RemoteRepository = new RepositoryModel( repository.Name, UriString.ToUriString(repository.CloneUrl.ToRepositoryUrl(parent.Value.owner))); Forks = new IRepositoryModel[] { RemoteRepository, repository, }; } this.WhenAnyValue(x => x.SelectedState, x => x.RemoteRepository) .Skip(1) .Subscribe(_ => Refresh().Forget()); Observable.Merge( this.WhenAnyValue(x => x.SearchQuery).Skip(1).SelectUnit(), AuthorFilter.WhenAnyValue(x => x.Selected).Skip(1).SelectUnit()) .Subscribe(_ => FilterChanged()); await Refresh(); } catch (Exception ex) { Error = ex; IsLoading = false; log.Error(ex, "Error initializing IssueListViewModelBase"); } }
public IObservable <bool> IsWorkingDirectoryClean(ILocalRepositoryModel repository) { // The `using` appears to resolve this issue: // https://github.com/github/VisualStudio/issues/1306 using (var repo = gitService.GetRepository(repository.LocalPath)) { var statusOptions = new StatusOptions { ExcludeSubmodules = true }; var status = repo.RetrieveStatus(statusOptions); var isClean = !IsCheckoutBlockingDirty(status); return(Observable.Return(isClean)); } }
async Task RepoChanged(ILocalRepositoryModel repository) { try { await ThreadingHelper.SwitchToMainThreadAsync(); await EnsureLoggedIn(repository); if (repository != this.repository) { this.repository = repository; CurrentSession = null; sessions.Clear(); } var modelService = hosts.LookupHost(HostAddress.Create(repository.CloneUrl))?.ModelService; var session = CurrentSession; if (modelService != null) { var pr = await service.GetPullRequestForCurrentBranch(repository).FirstOrDefaultAsync(); if (pr?.Item1 != (CurrentSession?.PullRequest.Base.RepositoryCloneUrl.Owner) && pr?.Item2 != (CurrentSession?.PullRequest.Number)) { var pullRequest = await GetPullRequestForTip(modelService, repository); if (pullRequest != null) { var newSession = await GetSessionInternal(pullRequest);; if (newSession != null) { newSession.IsCheckedOut = true; } session = newSession; } } } else { session = null; } CurrentSession = session; } catch { // TODO: Log } }
public async Task <string> GetGraphQLPullRequestId( ILocalRepositoryModel localRepository, string repositoryOwner, int number) { var address = HostAddress.Create(localRepository.CloneUrl.Host); var graphql = await graphqlFactory.CreateConnection(address); var query = new Query() .Repository(repositoryOwner, localRepository.Name) .PullRequest(number) .Select(x => x.Id); return((await graphql.Run(query)).Value); }
async Task RepoChanged(ILocalRepositoryModel localRepositoryModel) { repository = localRepositoryModel; CurrentSession = null; sessions.Clear(); if (localRepositoryModel != null) { await StatusChanged(); } else { initialized.TrySetResult(null); } }
/// <inheritdoc/> public async Task <PullRequestDetailModel> CreatePendingReview( ILocalRepositoryModel localRepository, string pullRequestId) { var address = HostAddress.Create(localRepository.CloneUrl.Host); var graphql = await graphqlFactory.CreateConnection(address); var(_, owner, number) = await CreatePendingReviewCore(localRepository, pullRequestId); var detail = await ReadPullRequestDetail(address, owner, localRepository.Name, number); await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentStartReview); return(detail); }
void RefreshCurrentSession(ILocalRepositoryModel repository, IPullRequestSession session) { var cloneUrl = repository?.CloneUrl; if (cloneUrl != null) { // Only show PR status bar if repo has remote var viewModel = CreatePullRequestStatusViewModel(session); ShowStatus(viewModel); } else { ShowStatus(null); } }
public IObservable <BranchTrackingDetails> CalculateHistoryDivergence(ILocalRepositoryModel repository, int pullRequestNumber) { return(Observable.Defer(async() => { var repo = gitService.GetRepository(repository.LocalPath); if (repo.Head.Remote != null) { var remote = await gitClient.GetHttpRemote(repo, repo.Head.Remote.Name); await gitClient.Fetch(repo, remote.Name); } return Observable.Return(repo.Head.TrackingDetails); })); }
public IObservable <IPullRequestModel> GetPullRequest(ILocalRepositoryModel repo, int number) { return(Observable.Defer(() => { return hostCache.GetAndRefreshObject(PRPrefix + '|' + number, () => Observable.CombineLatest( apiClient.GetPullRequest(repo.CloneUrl.Owner, repo.CloneUrl.RepositoryName, number), apiClient.GetPullRequestFiles(repo.CloneUrl.Owner, repo.CloneUrl.RepositoryName, number).ToList(), (pr, files) => new { PullRequest = pr, Files = files }) .Select(x => PullRequestCacheItem.Create(x.PullRequest, (IReadOnlyList <PullRequestFile>)x.Files)), TimeSpan.Zero, TimeSpan.FromDays(7)) .Select(Create); })); }
public Encoding GetEncoding(ILocalRepositoryModel repository, string relativePath) { var fullPath = Path.Combine(repository.LocalPath, relativePath); if (File.Exists(fullPath)) { var encoding = Encoding.UTF8; if (HasPreamble(fullPath, encoding)) { return(encoding); } } return(Encoding.Default); }
/// <inheritdoc/> public async Task <IPullRequestReviewCommentModel> PostStandaloneReviewCommentReply( ILocalRepositoryModel localRepository, IAccount user, string pullRequestNodeId, string body, string inReplyToNodeId) { var review = await CreatePendingReview(localRepository, user, pullRequestNodeId); var comment = await PostPendingReviewCommentReply(localRepository, user, review.NodeId, body, inReplyToNodeId); await SubmitPendingReview(localRepository, user, review.NodeId, null, PullRequestReviewEvent.Comment); comment.IsPending = false; return(comment); }
/// <inheritdoc/> public async Task DeleteComment( ILocalRepositoryModel localRepository, string remoteRepositoryOwner, IAccount user, int number) { var address = HostAddress.Create(localRepository.CloneUrl.Host); var apiClient = await apiClientFactory.Create(address); await apiClient.DeletePullRequestReviewComment( remoteRepositoryOwner, localRepository.Name, number); await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentDelete); }
public IObservable <IReadOnlyList <CommitMessage> > GetMessagesForUniqueCommits( ILocalRepositoryModel repository, string baseBranch, string compareBranch, int maxCommits) { return(Observable.Defer(async() => { // CommitMessage doesn't keep a reference to Repository using (var repo = gitService.GetRepository(repository.LocalPath)) { var messages = await gitClient.GetMessagesForUniqueCommits(repo, baseBranch, compareBranch, maxCommits); return Observable.Return(messages); } })); }
void HandleClonedRepo(ILocalRepositoryModel newrepo) { Guard.ArgumentNotNull(newrepo, nameof(newrepo)); var msg = string.Format(CultureInfo.CurrentCulture, Constants.Notification_RepoCloned, newrepo.Name, newrepo.CloneUrl); if (newrepo.HasCommits() && newrepo.MightContainSolution()) { msg += " " + string.Format(CultureInfo.CurrentCulture, Constants.Notification_OpenProject, newrepo.LocalPath); } else { msg += " " + string.Format(CultureInfo.CurrentCulture, Constants.Notification_CreateNewProject, newrepo.LocalPath); } ShowNotification(newrepo, msg); }
public IObservable <IPullRequestModel> CreatePullRequest(IModelService modelService, ILocalRepositoryModel sourceRepository, IRepositoryModel targetRepository, IBranch sourceBranch, IBranch targetBranch, string title, string body ) { Extensions.Guard.ArgumentNotNull(modelService, nameof(modelService)); 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(modelService, sourceRepository, targetRepository, sourceBranch, targetBranch, title, body).ToObservable()); }
public IObservable<IPullRequestModel> CreatePullRequest(IRepositoryHost host, ILocalRepositoryModel sourceRepository, IRepositoryModel 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(); }
/// <inheritdoc/> public async Task <IPullRequestReviewCommentModel> PostPendingReviewComment( ILocalRepositoryModel localRepository, IAccount user, string pendingReviewId, string body, string commitId, string path, int position) { var address = HostAddress.Create(localRepository.CloneUrl.Host); var graphql = await graphqlFactory.CreateConnection(address); var comment = new AddPullRequestReviewCommentInput { Body = body, CommitOID = commitId, Path = path, Position = position, PullRequestReviewId = pendingReviewId, }; var addComment = new Mutation() .AddPullRequestReviewComment(comment) .Select(x => new PullRequestReviewCommentModel { Id = x.Comment.DatabaseId.Value, NodeId = x.Comment.Id, Body = x.Comment.Body, CommitId = x.Comment.Commit.Oid, Path = x.Comment.Path, Position = x.Comment.Position, CreatedAt = x.Comment.CreatedAt.Value, DiffHunk = x.Comment.DiffHunk, OriginalPosition = x.Comment.OriginalPosition, OriginalCommitId = x.Comment.OriginalCommit.Oid, PullRequestReviewId = x.Comment.PullRequestReview.DatabaseId.Value, User = user, IsPending = true, }); var result = await graphql.Run(addComment); await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentPost); return(result); }
public async Task InitializeAsync( ILocalRepositoryModel localRepository, IConnection connection, string owner, string repo, int pullRequestNumber) { if (repo != localRepository.Name) { throw new NotSupportedException(); } IsLoading = true; try { LocalRepository = localRepository; RemoteRepositoryOwner = owner; session = await sessionManager.GetSession(owner, repo, pullRequestNumber).ConfigureAwait(true); await Load(session.PullRequest).ConfigureAwait(true); if (LocalRepository?.CloneUrl != null) { var key = GetDraftKey(); if (string.IsNullOrEmpty(Body)) { var draft = await draftStore.GetDraft <PullRequestReviewDraft>(key, string.Empty) .ConfigureAwait(true); Body = draft?.Body; } this.WhenAnyValue(x => x.Body) .Throttle(TimeSpan.FromSeconds(1), timerScheduler) .Select(x => new PullRequestReviewDraft { Body = x }) .Subscribe(x => draftStore.UpdateDraft(key, string.Empty, x)); } } finally { IsLoading = false; } }
async Task UpdateContent(ILocalRepositoryModel repository) { LocalRepository = repository; Connection = null; Content = null; if (repository == null) { Content = notAGitRepository; return; } else if (string.IsNullOrWhiteSpace(repository.CloneUrl)) { Content = notAGitHubRepository; return; } var repositoryUrl = repository.CloneUrl.ToRepositoryUrl(); var isDotCom = HostAddress.IsGitHubDotComUri(repositoryUrl); var client = await apiClientFactory.Create(repository.CloneUrl); var isEnterprise = isDotCom ? false : client.IsEnterprise(); if ((isDotCom || isEnterprise) && await IsValidRepository(client)) { var hostAddress = HostAddress.Create(repository.CloneUrl); Connection = await connectionManager.GetConnection(hostAddress); if (Connection != null) { navigator.Clear(); Content = navigator; await ShowDefaultPage(); } else { Content = loggedOut; } } else { Content = notAGitHubRepository; } }
public IObservable <string> GetDefaultLocalBranchName(ILocalRepositoryModel repository, int pullRequestNumber, string pullRequestTitle) { return(Observable.Defer(() => { var initial = "pr/" + pullRequestNumber + "-" + GetSafeBranchName(pullRequestTitle); var current = initial; var repo = gitService.GetRepository(repository.LocalPath); var index = 2; while (repo.Branches[current] != null) { current = initial + '-' + index++; } return Observable.Return(current); })); }
public void CreatePullRequestAllArgsMandatory() { var serviceProvider = Substitutes.ServiceProvider; var gitService = serviceProvider.GetGitService(); var service = new PullRequestService( Substitute.For <IGitClient>(), serviceProvider.GetGitService(), Substitute.For <IVSGitExt>(), Substitute.For <IGraphQLClientFactory>(), serviceProvider.GetOperatingSystem(), Substitute.For <IUsageTracker>()); IModelService ms = null; ILocalRepositoryModel sourceRepo = null; ILocalRepositoryModel targetRepo = null; string title = null; string body = null; IBranch source = null; IBranch target = null; Assert.Throws <ArgumentNullException>(() => service.CreatePullRequest(ms, sourceRepo, targetRepo, source, target, title, body)); ms = Substitute.For <IModelService>(); Assert.Throws <ArgumentNullException>(() => service.CreatePullRequest(ms, sourceRepo, targetRepo, source, target, title, body)); sourceRepo = new LocalRepositoryModel("name", new GitHub.Primitives.UriString("http://github.com/github/stuff"), "c:\\path", gitService); Assert.Throws <ArgumentNullException>(() => service.CreatePullRequest(ms, sourceRepo, targetRepo, source, target, title, body)); targetRepo = new LocalRepositoryModel("name", new GitHub.Primitives.UriString("http://github.com/github/stuff"), "c:\\path", gitService); Assert.Throws <ArgumentNullException>(() => service.CreatePullRequest(ms, sourceRepo, targetRepo, source, target, title, body)); title = "a title"; Assert.Throws <ArgumentNullException>(() => service.CreatePullRequest(ms, sourceRepo, targetRepo, source, target, title, body)); body = "a body"; Assert.Throws <ArgumentNullException>(() => service.CreatePullRequest(ms, sourceRepo, targetRepo, source, target, title, body)); source = new BranchModel("source", sourceRepo); Assert.Throws <ArgumentNullException>(() => service.CreatePullRequest(ms, sourceRepo, targetRepo, source, target, title, body)); target = new BranchModel("target", targetRepo); var pr = service.CreatePullRequest(ms, sourceRepo, targetRepo, source, target, title, body); Assert.NotNull(pr); }
void ShowNotification(ILocalRepositoryModel newrepo, string msg) { Guard.ArgumentNotNull(newrepo, nameof(newrepo)); var teServices = ServiceProvider.TryGetService <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.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:") { var vsGitServices = ServiceProvider.TryGetService <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 }
public PullRequestSession( IPullRequestSessionService service, IAccount user, IPullRequestModel pullRequest, ILocalRepositoryModel repository, bool isCheckedOut) { Guard.ArgumentNotNull(service, nameof(service)); Guard.ArgumentNotNull(user, nameof(user)); Guard.ArgumentNotNull(pullRequest, nameof(pullRequest)); Guard.ArgumentNotNull(repository, nameof(repository)); this.service = service; this.isCheckedOut = isCheckedOut; User = user; PullRequest = pullRequest; Repository = repository; }
async Task InitializeAsync( PullRequestUserReviewsViewModel target, ILocalRepositoryModel localRepository = null, IConnection connection = null, int pullRequestNumber = 5, string login = AuthorLogin) { localRepository = localRepository ?? CreateRepository(); connection = connection ?? Substitute.For <IConnection>(); await target.InitializeAsync( localRepository, connection, localRepository.Owner, localRepository.Name, pullRequestNumber, login); }
IEnumerable <string> GetLocalBranchesInternal( ILocalRepositoryModel localRepository, IRepository repository, IPullRequestModel pullRequest) { if (!IsPullRequestFromFork(localRepository, pullRequest)) { return(new[] { pullRequest.Head.Ref }); } else { var pr = pullRequest.Number.ToString(CultureInfo.InvariantCulture); return(repository.Config .Select(x => new { Branch = BranchCapture.Match(x.Key).Groups["branch"].Value, Value = x.Value }) .Where(x => !string.IsNullOrWhiteSpace(x.Branch) && x.Value == pr) .Select(x => x.Branch)); } }
public IObservable <string> GetPullRequestTemplate(ILocalRepositoryModel 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>(); })); }
/// <inheritdoc/> public async Task <PullRequestDetailModel> DeleteComment( ILocalRepositoryModel localRepository, string remoteRepositoryOwner, int pullRequestId, int commentDatabaseId) { var address = HostAddress.Create(localRepository.CloneUrl.Host); var apiClient = await apiClientFactory.Create(address); await apiClient.DeletePullRequestReviewComment( remoteRepositoryOwner, localRepository.Name, commentDatabaseId); await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentDelete); return(await ReadPullRequestDetail(address, remoteRepositoryOwner, localRepository.Name, pullRequestId)); }
public IObservable<string> GetPullRequestTemplate(ILocalRepositoryModel 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>(); }); }
public async Task InitializeAsync(ILocalRepositoryModel sourceRepository, IAccount destinationAccount, IConnection connection) { var modelService = await modelServiceFactory.CreateAsync(connection); apiClient = modelService.ApiClient; DestinationAccount = destinationAccount; SourceRepository = sourceRepository; DestinationRepository = new RemoteRepositoryModel( 0, sourceRepository.Name, CreateForkUri(sourceRepository.CloneUrl, destinationAccount.Login), false, true, destinationAccount, null); }
async Task<IPullRequestModel> PushAndCreatePR(IRepositoryHost host, ILocalRepositoryModel sourceRepository, IRepositoryModel 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); await usageTracker.IncrementUpstreamPullRequestCount(); return ret; }
void HandleCreatedRepo(ILocalRepositoryModel 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); }
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<ILocalRepositoryModel>().First(); SelectedRepository = newrepo; if (isCreating) HandleCreatedRepo(newrepo); else HandleClonedRepo(newrepo); isCreating = isCloning = false; try { // TODO: Cache the icon state. var repo = await ApiFactory.Create(newrepo.CloneUrl).GetRepository(); newrepo.SetIcon(repo.Private, repo.Fork); } catch { // GetRepository() may throw if the user doesn't have permissions to access the repo // (because the repo no longer exists, or because the user has logged in on a different // profile, or their permissions have changed remotely) // TODO: Log } } // looks like it's just a refresh with new stuff on the list, update the icons else { e.NewItems .Cast<ILocalRepositoryModel>() .ForEach(async r => { if (Equals(Holder.ActiveRepo, r)) SelectedRepository = r; try { // TODO: Cache the icon state. var repo = await ApiFactory.Create(r.CloneUrl).GetRepository(); r.SetIcon(repo.Private, repo.Fork); } catch { // GetRepository() may throw if the user doesn't have permissions to access the repo // (because the repo no longer exists, or because the user has logged in on a different // profile, or their permissions have changed remotely) // TODO: Log } }); } } }
public IObservable<Unit> Push(ILocalRepositoryModel repository) { return Observable.Defer(() => { var repo = gitService.GetRepository(repository.LocalPath); return gitClient.Push(repo, repo.Head.TrackedBranch.UpstreamBranchCanonicalName, repo.Head.Remote.Name).ToObservable(); }); }
public IObservable<Unit> Pull(ILocalRepositoryModel repository) { return Observable.Defer(() => { var repo = gitService.GetRepository(repository.LocalPath); return gitClient.Pull(repo).ToObservable(); }); }
public IObservable<bool> IsWorkingDirectoryClean(ILocalRepositoryModel repository) { var repo = gitService.GetRepository(repository.LocalPath); return Observable.Return(!repo.RetrieveStatus().IsDirty); }
public IObservable<IBranch> GetLocalBranches(ILocalRepositoryModel repository, IPullRequestModel pullRequest) { return Observable.Defer(() => { var repo = gitService.GetRepository(repository.LocalPath); var result = GetLocalBranchesInternal(repository, repo, pullRequest).Select(x => new BranchModel(x, repository)); return result.ToObservable(); }); }
public IObservable<IPullRequestModel> GetPullRequest(ILocalRepositoryModel repo, int number) { return Observable.Defer(() => { return hostCache.GetAndRefreshObject(PRPrefix + '|' + number, () => Observable.CombineLatest( apiClient.GetPullRequest(repo.CloneUrl.Owner, repo.CloneUrl.RepositoryName, number), apiClient.GetPullRequestFiles(repo.CloneUrl.Owner, repo.CloneUrl.RepositoryName, number).ToList(), (pr, files) => new { PullRequest = pr, Files = files }) .Select(x => PullRequestCacheItem.Create(x.PullRequest, (IReadOnlyList<PullRequestFile>)x.Files)), TimeSpan.Zero, TimeSpan.FromDays(7)) .Select(Create); }); }
public IObservable<IPullRequestModel> CreatePullRequest(ILocalRepositoryModel sourceRepository, IRepositoryModel targetRepository, IBranch sourceBranch, IBranch targetBranch, string title, string body) { var keyobs = GetUserFromCache() .Select(user => string.Format(CultureInfo.InvariantCulture, "{0}|{1}:{2}", CacheIndex.PRPrefix, targetRepository.Owner, targetRepository.Name)); return Observable.Defer(() => keyobs .SelectMany(key => hostCache.PutAndUpdateIndex(key, () => apiClient.CreatePullRequest( new NewPullRequest(title, string.Format(CultureInfo.InvariantCulture, "{0}:{1}", sourceRepository.Owner, sourceBranch.Name), targetBranch.Name) { Body = body }, targetRepository.Owner, targetRepository.Name) .Select(PullRequestCacheItem.Create) , TimeSpan.FromMinutes(30)) ) .Select(Create) ); }
IEnumerable<string> GetLocalBranchesInternal( ILocalRepositoryModel localRepository, IRepository repository, IPullRequestModel pullRequest) { if (!IsPullRequestFromFork(localRepository, pullRequest)) { return new[] { pullRequest.Head.Ref }; } else { var pr = pullRequest.Number.ToString(CultureInfo.InvariantCulture); return repository.Config .Select(x => new { Branch = BranchCapture.Match(x.Key).Groups["branch"].Value, Value = x.Value }) .Where(x => !string.IsNullOrWhiteSpace(x.Branch) && x.Value == pr) .Select(x => x.Branch); } }
void ShowNotification(ILocalRepositoryModel 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 }
/// <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(ILocalRepositoryModel 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; }
public IObservable<Unit> UnmarkLocalBranch(ILocalRepositoryModel repository) { return Observable.Defer(async () => { var repo = gitService.GetRepository(repository.LocalPath); var configKey = $"branch.{repo.Head.FriendlyName}.ghfvs-pr"; await gitClient.UnsetConfig(repo, configKey); return Observable.Return(Unit.Default); }); }
public IObservable<Unit> SwitchToBranch(ILocalRepositoryModel repository, IPullRequestModel pullRequest) { return Observable.Defer(async () => { var repo = gitService.GetRepository(repository.LocalPath); var branchName = GetLocalBranchesInternal(repository, repo, pullRequest).FirstOrDefault(); Debug.Assert(branchName != null, "PullRequestService.SwitchToBranch called but no local branch found."); if (branchName != null) { await gitClient.Fetch(repo, "origin"); var branch = repo.Branches[branchName]; if (branch == null) { var trackedBranchName = $"refs/remotes/origin/" + branchName; var trackedBranch = repo.Branches[trackedBranchName]; if (trackedBranch != null) { branch = repo.CreateBranch(branchName, trackedBranch.Tip); await gitClient.SetTrackingBranch(repo, branchName, trackedBranchName); } else { throw new InvalidOperationException($"Could not find branch '{trackedBranchName}'."); } } await gitClient.Checkout(repo, branchName); } return Observable.Empty<Unit>(); }); }
public bool IsPullRequestFromFork(ILocalRepositoryModel repository, IPullRequestModel pullRequest) { return pullRequest.Head.RepositoryCloneUrl?.ToRepositoryUrl() != repository.CloneUrl.ToRepositoryUrl(); }
public PullRequestCreationViewModel(IRepositoryHost repositoryHost, ILocalRepositoryModel 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 RemoteRepositoryModel(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, SourceBranch.DisplayName, TargetBranch.Repository.Owner + "/" + TargetBranch.Repository.Name + "#" + pr.Number, 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); }
public IObservable<string> GetDefaultLocalBranchName(ILocalRepositoryModel repository, int pullRequestNumber, string pullRequestTitle) { return Observable.Defer(() => { var initial = "pr/" + pullRequestNumber + "-" + GetSafeBranchName(pullRequestTitle); var current = initial; var repo = gitService.GetRepository(repository.LocalPath); var index = 2; while (repo.Branches[current] != null) { current = initial + '-' + index++; } return Observable.Return(current); }); }
public static IObservable<IConnection> LookupConnection(this IConnectionManager cm, ILocalRepositoryModel repository) { return Observable.Return(repository?.CloneUrl != null ? cm.Connections.FirstOrDefault(c => c.HostAddress.Equals(HostAddress.Create(repository.CloneUrl))) : null); }
public IObservable<BranchTrackingDetails> CalculateHistoryDivergence(ILocalRepositoryModel repository, int pullRequestNumber) { return Observable.Defer(async () => { var repo = gitService.GetRepository(repository.LocalPath); await gitClient.Fetch(repo, repo.Head.Remote.Name); return Observable.Return(repo.Head.TrackingDetails); }); }
public IObservable<string> ExtractFile(ILocalRepositoryModel repository, string commitSha, string fileName) { return Observable.Defer(async () => { var repo = gitService.GetRepository(repository.LocalPath); await gitClient.Fetch(repo, "origin"); var result = await gitClient.ExtractFile(repo, commitSha, fileName); return Observable.Return(result); }); }
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; }
public IObservable<Tuple<string, string>> ExtractDiffFiles(ILocalRepositoryModel repository, IPullRequestModel pullRequest, string fileName) { return Observable.Defer(async () => { var repo = gitService.GetRepository(repository.LocalPath); await gitClient.Fetch(repo, "origin"); var left = await gitClient.ExtractFile(repo, pullRequest.Base.Sha, fileName); var right = await gitClient.ExtractFile(repo, pullRequest.Head.Sha, fileName); return Observable.Return(Tuple.Create(left, right)); }); }
public IObservable<Unit> RemoteUnusedRemotes(ILocalRepositoryModel repository) { return Observable.Defer(async () => { var repo = gitService.GetRepository(repository.LocalPath); var usedRemotes = new HashSet<string>( repo.Branches .Where(x => !x.IsRemote && x.Remote != null) .Select(x => x.Remote?.Name)); foreach (var remote in repo.Network.Remotes) { var key = $"remote.{remote.Name}.{SettingCreatedByGHfVS}"; var createdByUs = await gitClient.GetConfig<bool>(repo, key); if (createdByUs && !usedRemotes.Contains(remote.Name)) { repo.Network.Remotes.Remove(remote.Name); } } return Observable.Return(Unit.Default); }); }
public IObservable<Unit> Checkout(ILocalRepositoryModel repository, IPullRequestModel pullRequest, string localBranchName) { return Observable.Defer(async () => { var repo = gitService.GetRepository(repository.LocalPath); var existing = repo.Branches[localBranchName]; if (existing != null) { await gitClient.Checkout(repo, localBranchName); } else if (repository.CloneUrl.ToRepositoryUrl() == pullRequest.Head.RepositoryCloneUrl.ToRepositoryUrl()) { await gitClient.Fetch(repo, "origin"); await gitClient.Checkout(repo, localBranchName); } else { var refSpec = $"{pullRequest.Head.Ref}:{localBranchName}"; var prConfigKey = $"branch.{localBranchName}.{SettingGHfVSPullRequest}"; var remoteName = await CreateRemote(repo, pullRequest.Head.RepositoryCloneUrl); await gitClient.Fetch(repo, remoteName); await gitClient.Fetch(repo, remoteName, new[] { refSpec }); await gitClient.Checkout(repo, localBranchName); await gitClient.SetTrackingBranch(repo, localBranchName, $"refs/remotes/{remoteName}/{pullRequest.Head.Ref}"); await gitClient.SetConfig(repo, prConfigKey, pullRequest.Number.ToString()); } return Observable.Return(Unit.Default); }); }
void HandleClonedRepo(ILocalRepositoryModel 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); }