зеркало из https://github.com/github/VisualStudio.git
Merge branch 'master' into fixes/1859-missing-MEF-exports
This commit is contained in:
Коммит
e4d831b5e0
|
@ -13,16 +13,16 @@ This project adheres to the [Open Code of Conduct][code-of-conduct]. By particip
|
|||
|
||||
## Submitting a pull request
|
||||
|
||||
0. [Fork][] and clone the repository (see Build Instructions in the [README][readme])
|
||||
0. Create a new branch: `git checkout -b my-branch-name`
|
||||
0. Make your change, add tests, and make sure the tests still pass
|
||||
0. Push to your fork and [submit a pull request][pr]
|
||||
0. Pat your self on the back and wait for your pull request to be reviewed and merged.
|
||||
1. [Fork][] and clone the repository (see Build Instructions in the [README][readme])
|
||||
2. Create a new branch: `git checkout -b my-branch-name`
|
||||
3. Make your change, add tests, and make sure the tests still pass
|
||||
4. Push to your fork and [submit a pull request][pr]
|
||||
5. Pat your self on the back and wait for your pull request to be reviewed and merged.
|
||||
|
||||
Here are a few things you can do that will increase the likelihood of your pull request being accepted:
|
||||
|
||||
- Follow the existing code's style.
|
||||
- Write tests.
|
||||
- Follow the style/format of the existing code.
|
||||
- Write tests for your changes.
|
||||
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
|
||||
- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
|
||||
|
@ -38,7 +38,7 @@ There are certain areas of the extension that are restricted in what they can do
|
|||
### Bug Reporting
|
||||
|
||||
Here are a few helpful tips when reporting a bug:
|
||||
- Verify the bug resides in the GitHub for Visual Studio extension
|
||||
- Verify that the bug resides in the GitHub for Visual Studio extension
|
||||
- A lot of functionality provided by this extension resides in the Team Explorer pane, alongside other non-GitHub tools to manage and collaborate on source code, including Visual Studio's Git support, which is owned by Microsoft.
|
||||
- If this bug not is related to the GitHub extension, visit the [Visual Studio support page](https://www.visualstudio.com/support/support-overview-vs) for help
|
||||
- Screenshots are very helpful in diagnosing bugs and understanding the state of the extension when it's experiencing problems. Please include them whenever possible.
|
||||
|
|
|
@ -1,6 +1,24 @@
|
|||
Hello! Please read the contributing guidelines before submitting an issue regarding the GitHub Extension for Visual Studio.
|
||||
<!-- Hello! Please read the [contributing guidelines](https://github.com/github/VisualStudio/blob/master/CONTRIBUTING.md) before submitting an issue regarding the GitHub Extension for Visual Studio. -->
|
||||
|
||||
## Version
|
||||
|
||||
- GitHub Extension for Visual Studio version:
|
||||
- Visual Studio version:
|
||||
|
||||
__What happened__ (with steps, logs and screenshots, if possible)
|
||||
## What happened
|
||||
|
||||
**Steps to Reproduce**
|
||||
|
||||
1. [First Step]
|
||||
2. [Second Step]
|
||||
3. [and so on...]
|
||||
|
||||
**Expected behavior:** [What you expect to happen]
|
||||
|
||||
**Actual behavior:** [What actually happens]
|
||||
|
||||
**Screenshot or GIF:**
|
||||
|
||||
**Log file:**
|
||||
|
||||
<!-- Include the log file. Logs can be found at: `%LOCALAPPDATA%\GitHubVisualStudio\extension.log` -->
|
||||
|
|
|
@ -85,6 +85,10 @@ To build and install a `Release` configuration VSIX:
|
|||
build.cmd Release
|
||||
install.cmd Release
|
||||
```
|
||||
## Logs
|
||||
Logs can be viewed at the following location:
|
||||
|
||||
`%LOCALAPPDATA%\GitHubVisualStudio\extension.log`
|
||||
|
||||
## More information
|
||||
- Andreia Gaita's [presentation](https://www.youtube.com/watch?v=hz2hCO8e_8w) at Codemania 2016 about this extension.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
os: Visual Studio 2017
|
||||
version: '2.5.4.{build}'
|
||||
version: '2.5.5.{build}'
|
||||
skip_tags: true
|
||||
install:
|
||||
- ps: |
|
||||
|
|
|
@ -65,8 +65,8 @@ This is the basis for converting view models to views.
|
|||
|
||||
There are currently two top-level controls for our UI:
|
||||
|
||||
- [GitHubDialogWindow](../src/GitHub.VisualStudio/Views/Dialog/GitHubDialogWindow.xaml) for the dialog which shows the login, clone, etc views
|
||||
- [GitHubPaneView](../src/GitHub.VisualStudio/Views/GitHubPane/GitHubPaneView.xaml) for the GitHub pane
|
||||
- [GitHubDialogWindow](../../src/GitHub.VisualStudio/Views/Dialog/GitHubDialogWindow.xaml) for the dialog which shows the login, clone, etc views
|
||||
- [GitHubPaneView](../../src/GitHub.VisualStudio/Views/GitHubPane/GitHubPaneView.xaml) for the GitHub pane
|
||||
|
||||
In the resources for each of these top-level controls we define a `DataTemplate` like so:
|
||||
|
||||
|
@ -77,10 +77,10 @@ In the resources for each of these top-level controls we define a `DataTemplate`
|
|||
</DataTemplate>
|
||||
```
|
||||
|
||||
The `DataTemplate.DataType` here applies the template to all classes inherited from [`GitHub.ViewModels.ViewModelBase`](../src/GitHub.Exports.Reactive/ViewModels/ViewModelBase.cs) [1]. The template defines a single `ContentControl` whose contents are created by a `ViewLocator`.
|
||||
The `DataTemplate.DataType` here applies the template to all classes inherited from [`GitHub.ViewModels.ViewModelBase`](../../src/GitHub.Exports.Reactive/ViewModels/ViewModelBase.cs) [1]. The template defines a single `ContentControl` whose contents are created by a `ViewLocator`.
|
||||
|
||||
The [`ViewLocator`](../src/GitHub.VisualStudio/Views/ViewLocator.cs) class is an `IValueConverter` which then creates an instance of the appropriate view for the view model using MEF.
|
||||
The [`ViewLocator`](../../src/GitHub.VisualStudio/Views/ViewLocator.cs) class is an `IValueConverter` which then creates an instance of the appropriate view for the view model using MEF.
|
||||
|
||||
And thus a view model becomes a view.
|
||||
|
||||
[1]: it would be nice to make it apply to all classes that inherit `IViewModel` but unfortunately WPF's `DataTemplate`s don't work with interfaces.
|
||||
[1]: it would be nice to make it apply to all classes that inherit `IViewModel` but unfortunately WPF's `DataTemplate`s don't work with interfaces.
|
||||
|
|
Двоичный файл не отображается.
|
@ -50,11 +50,11 @@
|
|||
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Octokit.GraphQL, Version=0.1.0.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Octokit.GraphQL.0.1.0-beta\lib\netstandard1.1\Octokit.GraphQL.dll</HintPath>
|
||||
<Reference Include="Octokit.GraphQL, Version=0.1.1.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Octokit.GraphQL.Core, Version=0.1.0.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Octokit.GraphQL.0.1.0-beta\lib\netstandard1.1\Octokit.GraphQL.Core.dll</HintPath>
|
||||
<Reference Include="Octokit.GraphQL.Core, Version=0.1.1.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll</HintPath>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Extensions;
|
||||
using GitHub.Primitives;
|
||||
|
@ -23,7 +24,7 @@ namespace GitHub.Api
|
|||
this.address = address;
|
||||
}
|
||||
|
||||
public async Task<string> GetCredentials()
|
||||
public async Task<string> GetCredentials(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var userPass = await keychain.Load(address).ConfigureAwait(false);
|
||||
return userPass?.Item2;
|
||||
|
|
|
@ -72,6 +72,7 @@ namespace GitHub.Api
|
|||
/// Logs out of GitHub server.
|
||||
/// </summary>
|
||||
/// <param name="hostAddress">The address of the server.</param>
|
||||
/// <param name="client">An octokit client configured to access the server.</param>
|
||||
Task Logout(HostAddress hostAddress, IGitHubClient client);
|
||||
}
|
||||
}
|
|
@ -34,8 +34,10 @@ namespace GitHub.Api
|
|||
/// </summary>
|
||||
/// <param name="keychain">The keychain in which to store credentials.</param>
|
||||
/// <param name="twoFactorChallengeHandler">The handler for 2FA challenges.</param>
|
||||
/// <param name="oauthListener">The callback listener to signal successful login.</param>
|
||||
/// <param name="clientId">The application's client API ID.</param>
|
||||
/// <param name="clientSecret">The application's client API secret.</param>
|
||||
/// <param name="scopes">List of scopes to authenticate for</param>
|
||||
/// <param name="authorizationNote">An note to store with the authorization.</param>
|
||||
/// <param name="fingerprint">The machine fingerprint.</param>
|
||||
public LoginManager(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" />
|
||||
<package id="Octokit.GraphQL" version="0.1.0-beta" targetFramework="net461" />
|
||||
<package id="Octokit.GraphQL" version="0.1.1-beta" targetFramework="net461" />
|
||||
<package id="Serilog" version="2.5.0" targetFramework="net461" />
|
||||
</packages>
|
|
@ -252,11 +252,12 @@ namespace GitHub.Extensions
|
|||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="blobCache">The cache to retrieve the object from.</param>
|
||||
/// <param name="key">The key to look up the cache value with.</param>
|
||||
/// <param name="item">The item to add to the database</param>
|
||||
/// <param name="fetchFunc">The fetch function.</param>
|
||||
/// <param name="maxCacheDuration">
|
||||
/// The maximum age of a cache object before the object is treated as
|
||||
/// expired and unusable. Cache objects older than this will be treated
|
||||
/// as a cache miss.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public static IObservable<T> PutAndUpdateIndex<T>(this IBlobCache blobCache,
|
||||
string key,
|
||||
|
|
|
@ -144,11 +144,11 @@
|
|||
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Octokit.GraphQL, Version=0.1.0.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Octokit.GraphQL.0.1.0-beta\lib\netstandard1.1\Octokit.GraphQL.dll</HintPath>
|
||||
<Reference Include="Octokit.GraphQL, Version=0.1.1.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Octokit.GraphQL.Core, Version=0.1.0.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Octokit.GraphQL.0.1.0-beta\lib\netstandard1.1\Octokit.GraphQL.Core.dll</HintPath>
|
||||
<Reference Include="Octokit.GraphQL.Core, Version=0.1.1.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
|
@ -197,6 +197,7 @@
|
|||
<HintPath>..\..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xaml" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
|
@ -228,6 +229,7 @@
|
|||
<Compile Include="SampleData\PullRequestUserReviewsViewModelDesigner.cs" />
|
||||
<Compile Include="SampleData\UserFilterViewModelDesigner.cs" />
|
||||
<Compile Include="Services\EnterpriseCapabilitiesService.cs" />
|
||||
<Compile Include="Services\FromGraphQlExtensions.cs" />
|
||||
<Compile Include="Services\GitHubContextService.cs" />
|
||||
<Compile Include="Services\GlobalConnection.cs" />
|
||||
<Compile Include="Services\RepositoryForkService.cs" />
|
||||
|
@ -257,6 +259,9 @@
|
|||
<Compile Include="ViewModels\GitHubPane\LoggedOutViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\NavigationViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\GitHubPaneViewModel.cs" />
|
||||
<Compile Include="SampleData\PullRequestCheckViewModelDesigner.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\LoginFailedViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\PullRequestCheckType.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\PullRequestFilesViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\PullRequestListItemViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\PullRequestListViewModel.cs" />
|
||||
|
@ -267,6 +272,7 @@
|
|||
<Compile Include="ViewModels\GitHubPane\NotAGitRepositoryViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\PullRequestReviewAuthoringViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\PullRequestReviewCommentViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\PullRequestCheckViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\PullRequestReviewSummaryViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\PullRequestReviewViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\PullRequestUserReviewsViewModel.cs" />
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace GitHub.App {
|
|||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
|
@ -87,6 +87,24 @@ namespace GitHub.App {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Are you sure you want to cancel this review? You will lose all your pending comments..
|
||||
/// </summary>
|
||||
public static string CancelPendingReviewConfirmation {
|
||||
get {
|
||||
return ResourceManager.GetString("CancelPendingReviewConfirmation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Cancel Review.
|
||||
/// </summary>
|
||||
public static string CancelPendingReviewConfirmationCaption {
|
||||
get {
|
||||
return ResourceManager.GetString("CancelPendingReviewConfirmationCaption", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Changes Requested.
|
||||
/// </summary>
|
||||
|
@ -210,7 +228,7 @@ namespace GitHub.App {
|
|||
/// <summary>
|
||||
/// Looks up a localized string similar to Fork Repository.
|
||||
/// </summary>
|
||||
internal static string ForkRepositoryTitle {
|
||||
public static string ForkRepositoryTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("ForkRepositoryTitle", resourceCulture);
|
||||
}
|
||||
|
@ -588,7 +606,7 @@ namespace GitHub.App {
|
|||
/// <summary>
|
||||
/// Looks up a localized string similar to Switch Origin.
|
||||
/// </summary>
|
||||
internal static string SwitchOriginTitle {
|
||||
public static string SwitchOriginTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("SwitchOriginTitle", resourceCulture);
|
||||
}
|
||||
|
|
|
@ -321,4 +321,10 @@ https://git-scm.com/download/win</value>
|
|||
<data name="SwitchOriginTitle" xml:space="preserve">
|
||||
<value>Switch Origin</value>
|
||||
</data>
|
||||
<data name="CancelPendingReviewConfirmation" xml:space="preserve">
|
||||
<value>Are you sure you want to cancel this review? You will lose all your pending comments.</value>
|
||||
</data>
|
||||
<data name="CancelPendingReviewConfirmationCaption" xml:space="preserve">
|
||||
<value>Cancel Review</value>
|
||||
</data>
|
||||
</root>
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using System.Windows.Media.Imaging;
|
||||
using GitHub.ViewModels;
|
||||
using GitHub.ViewModels.GitHubPane;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace GitHub.SampleData
|
||||
{
|
||||
public sealed class PullRequestCheckViewModelDesigner : ViewModelBase, IPullRequestCheckViewModel
|
||||
{
|
||||
public string Title { get; set; } = "continuous-integration/appveyor/pr";
|
||||
|
||||
public string Description { get; set; } = "AppVeyor build failed";
|
||||
|
||||
public PullRequestCheckStatus Status { get; set; } = PullRequestCheckStatus.Failure;
|
||||
|
||||
public Uri DetailsUrl { get; set; } = new Uri("http://github.com");
|
||||
|
||||
public ReactiveCommand<object> OpenDetailsUrl { get; set; } = null;
|
||||
}
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reactive;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Models;
|
||||
using GitHub.Services;
|
||||
using GitHub.ViewModels;
|
||||
using GitHub.ViewModels.GitHubPane;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reactive;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.SampleData;
|
||||
|
||||
namespace GitHub.SampleData
|
||||
{
|
||||
|
@ -95,6 +95,8 @@ This requires that errors be propagated from the viewmodel to the view and from
|
|||
};
|
||||
|
||||
Files = new PullRequestFilesViewModelDesigner();
|
||||
|
||||
Checks = new PullRequestCheckViewModelDesigner[0];
|
||||
}
|
||||
|
||||
public PullRequestDetailModel Model { get; }
|
||||
|
@ -123,6 +125,8 @@ This requires that errors be propagated from the viewmodel to the view and from
|
|||
public ReactiveCommand<object> OpenOnGitHub { get; }
|
||||
public ReactiveCommand<object> ShowReview { get; }
|
||||
|
||||
public IReadOnlyList<IPullRequestCheckViewModel> Checks { get; }
|
||||
|
||||
public Task InitializeAsync(ILocalRepositoryModel localRepository, IConnection connection, string owner, string repo, int number) => Task.CompletedTask;
|
||||
|
||||
public string GetLocalFilePath(IPullRequestFileNode file)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using GitHub.Models;
|
||||
using GitHub.ViewModels;
|
||||
using GitHub.ViewModels.GitHubPane;
|
||||
|
||||
|
@ -16,5 +17,6 @@ namespace GitHub.SampleData
|
|||
public int Number { get; set; }
|
||||
public string Title { get; set; }
|
||||
public DateTimeOffset UpdatedAt { get; set; }
|
||||
public PullRequestChecksState Checks { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reactive;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -201,8 +202,15 @@ namespace GitHub.SampleData
|
|||
|
||||
public Octokit.User User => null;
|
||||
public bool IsLoggedIn => true;
|
||||
public bool IsLoggingIn => false;
|
||||
|
||||
public Exception ConnectionError => null;
|
||||
|
||||
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
}
|
||||
|
||||
public RepositoryPublishViewModelDesigner()
|
||||
|
@ -421,12 +429,21 @@ namespace GitHub.SampleData
|
|||
{
|
||||
}
|
||||
|
||||
public void Retry()
|
||||
{
|
||||
}
|
||||
|
||||
public bool OpenRepository()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public string ErrorMessage { get; set; }
|
||||
public IConnection SectionConnection { get; }
|
||||
public bool IsLoggingIn { get; set; }
|
||||
public bool ShowLogin { get; set; }
|
||||
public bool ShowLogout { get; set; }
|
||||
public bool ShowRetry { get; set; }
|
||||
public ICommand Clone { get; }
|
||||
}
|
||||
|
||||
|
|
|
@ -51,10 +51,19 @@ namespace GitHub.Services
|
|||
return (string)await showDialog.ShowWithFirstConnection(viewModel);
|
||||
}
|
||||
|
||||
public async Task ShowCreateGist()
|
||||
public async Task ShowCreateGist(IConnection connection)
|
||||
{
|
||||
var viewModel = factory.CreateViewModel<IGistCreationViewModel>();
|
||||
await showDialog.ShowWithFirstConnection(viewModel);
|
||||
|
||||
if (connection != null)
|
||||
{
|
||||
await viewModel.InitializeAsync(connection);
|
||||
await showDialog.Show(viewModel);
|
||||
}
|
||||
else
|
||||
{
|
||||
await showDialog.ShowWithFirstConnection(viewModel);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ShowCreateRepositoryDialog(IConnection connection)
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
using System;
|
||||
using GitHub.Models;
|
||||
using Octokit.GraphQL.Model;
|
||||
using CheckConclusionState = GitHub.Models.CheckConclusionState;
|
||||
using CheckStatusState = GitHub.Models.CheckStatusState;
|
||||
using PullRequestReviewState = GitHub.Models.PullRequestReviewState;
|
||||
using StatusState = GitHub.Models.StatusState;
|
||||
|
||||
namespace GitHub.Services
|
||||
{
|
||||
public static class FromGraphQlExtensions
|
||||
{
|
||||
public static CheckConclusionState? FromGraphQl(this Octokit.GraphQL.Model.CheckConclusionState? value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case null:
|
||||
return null;
|
||||
case Octokit.GraphQL.Model.CheckConclusionState.ActionRequired:
|
||||
return CheckConclusionState.ActionRequired;
|
||||
case Octokit.GraphQL.Model.CheckConclusionState.TimedOut:
|
||||
return CheckConclusionState.TimedOut;
|
||||
case Octokit.GraphQL.Model.CheckConclusionState.Cancelled:
|
||||
return CheckConclusionState.Cancelled;
|
||||
case Octokit.GraphQL.Model.CheckConclusionState.Failure:
|
||||
return CheckConclusionState.Failure;
|
||||
case Octokit.GraphQL.Model.CheckConclusionState.Success:
|
||||
return CheckConclusionState.Success;
|
||||
case Octokit.GraphQL.Model.CheckConclusionState.Neutral:
|
||||
return CheckConclusionState.Neutral;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(value), value, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static PullRequestStateEnum FromGraphQl(this PullRequestState value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case PullRequestState.Open:
|
||||
return PullRequestStateEnum.Open;
|
||||
case PullRequestState.Closed:
|
||||
return PullRequestStateEnum.Closed;
|
||||
case PullRequestState.Merged:
|
||||
return PullRequestStateEnum.Merged;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(value), value, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static StatusState FromGraphQl(this Octokit.GraphQL.Model.StatusState value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case Octokit.GraphQL.Model.StatusState.Expected:
|
||||
return StatusState.Expected;
|
||||
case Octokit.GraphQL.Model.StatusState.Error:
|
||||
return StatusState.Error;
|
||||
case Octokit.GraphQL.Model.StatusState.Failure:
|
||||
return StatusState.Failure;
|
||||
case Octokit.GraphQL.Model.StatusState.Pending:
|
||||
return StatusState.Pending;
|
||||
case Octokit.GraphQL.Model.StatusState.Success:
|
||||
return StatusState.Success;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(value), value, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static CheckStatusState FromGraphQl(this Octokit.GraphQL.Model.CheckStatusState value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case Octokit.GraphQL.Model.CheckStatusState.Queued:
|
||||
return CheckStatusState.Queued;
|
||||
case Octokit.GraphQL.Model.CheckStatusState.InProgress:
|
||||
return CheckStatusState.InProgress;
|
||||
case Octokit.GraphQL.Model.CheckStatusState.Completed:
|
||||
return CheckStatusState.Completed;
|
||||
case Octokit.GraphQL.Model.CheckStatusState.Requested:
|
||||
return CheckStatusState.Requested;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(value), value, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static GitHub.Models.PullRequestReviewState FromGraphQl(this Octokit.GraphQL.Model.PullRequestReviewState value)
|
||||
{
|
||||
switch (value) {
|
||||
case Octokit.GraphQL.Model.PullRequestReviewState.Pending:
|
||||
return PullRequestReviewState.Pending;
|
||||
case Octokit.GraphQL.Model.PullRequestReviewState.Commented:
|
||||
return PullRequestReviewState.Commented;
|
||||
case Octokit.GraphQL.Model.PullRequestReviewState.Approved:
|
||||
return PullRequestReviewState.Approved;
|
||||
case Octokit.GraphQL.Model.PullRequestReviewState.ChangesRequested:
|
||||
return PullRequestReviewState.ChangesRequested;
|
||||
case Octokit.GraphQL.Model.PullRequestReviewState.Dismissed:
|
||||
return PullRequestReviewState.Dismissed;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(value), value, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -58,6 +58,8 @@ namespace GitHub.Services
|
|||
static readonly Regex treeishCommitRegex = new Regex($"(?<commit>[a-z0-9]{{40}})(/(?<tree>.+))?", RegexOptions.Compiled);
|
||||
static readonly Regex treeishBranchRegex = new Regex($"(?<branch>master)(/(?<tree>.+))?", RegexOptions.Compiled);
|
||||
|
||||
static readonly Regex tempFileObjectishRegex = new Regex(@"\\TFSTemp\\[^\\]*[.](?<objectish>[a-z0-9]{8})[.][^.\\]*$", RegexOptions.Compiled);
|
||||
|
||||
[ImportingConstructor]
|
||||
public GitHubContextService(IGitHubServiceProvider serviceProvider, IGitService gitService)
|
||||
{
|
||||
|
@ -305,6 +307,55 @@ namespace GitHub.Services
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string FindObjectishForTFSTempFile(string tempFile)
|
||||
{
|
||||
var match = tempFileObjectishRegex.Match(tempFile);
|
||||
if (match.Success)
|
||||
{
|
||||
return match.Groups["objectish"].Value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public (string commitSha, string blobPath) ResolveBlobFromHistory(string repositoryDir, string objectish)
|
||||
{
|
||||
using (var repo = gitService.GetRepository(repositoryDir))
|
||||
{
|
||||
var blob = repo.Lookup<Blob>(objectish);
|
||||
if (blob == null)
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
foreach (var commit in repo.Commits)
|
||||
{
|
||||
var trees = new Stack<Tree>();
|
||||
trees.Push(commit.Tree);
|
||||
|
||||
while (trees.Count > 0)
|
||||
{
|
||||
foreach (var treeEntry in trees.Pop())
|
||||
{
|
||||
if (treeEntry.Target == blob)
|
||||
{
|
||||
return (commit.Sha, treeEntry.Path);
|
||||
}
|
||||
|
||||
if (treeEntry.TargetType == TreeEntryTargetType.Tree)
|
||||
{
|
||||
trees.Push((Tree)treeEntry.Target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (null, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool HasChangesInWorkingDirectory(string repositoryDir, string commitish, string path)
|
||||
{
|
||||
|
|
|
@ -336,6 +336,7 @@ namespace GitHub.Services
|
|||
/// <param name="fromLines">The document we're navigating from.</param>
|
||||
/// <param name="toLines">The document we're navigating to.</param>
|
||||
/// <param name="line">The 0-based line we're navigating from.</param>
|
||||
/// <param name="matchLinesAbove"></param>
|
||||
/// <returns>The best matching line in <see cref="toLines"/></returns>
|
||||
public int FindMatchingLine(IList<string> fromLines, IList<string> toLines, int line, int matchLinesAbove = 0)
|
||||
{
|
||||
|
|
|
@ -11,7 +11,9 @@ using System.Reactive.Threading.Tasks;
|
|||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using GitHub.Api;
|
||||
using GitHub.App.Services;
|
||||
using GitHub.Extensions;
|
||||
using GitHub.Logging;
|
||||
using GitHub.Models;
|
||||
|
@ -22,6 +24,9 @@ using Octokit.GraphQL.Model;
|
|||
using Rothko;
|
||||
using static System.FormattableString;
|
||||
using static Octokit.GraphQL.Variable;
|
||||
using CheckConclusionState = GitHub.Models.CheckConclusionState;
|
||||
using CheckStatusState = GitHub.Models.CheckStatusState;
|
||||
using StatusState = GitHub.Models.StatusState;
|
||||
|
||||
namespace GitHub.Services
|
||||
{
|
||||
|
@ -36,6 +41,7 @@ namespace GitHub.Services
|
|||
static readonly Regex BranchCapture = new Regex(@"branch\.(?<branch>.+)\.ghfvs-pr", RegexOptions.ECMAScript);
|
||||
static ICompiledQuery<Page<ActorModel>> readAssignableUsers;
|
||||
static ICompiledQuery<Page<PullRequestListItemModel>> readPullRequests;
|
||||
static ICompiledQuery<Page<PullRequestListItemModel>> readPullRequestsEnterprise;
|
||||
|
||||
static readonly string[] TemplatePaths = new[]
|
||||
{
|
||||
|
@ -76,40 +82,120 @@ namespace GitHub.Services
|
|||
string after,
|
||||
PullRequestStateEnum[] states)
|
||||
{
|
||||
if (readPullRequests == null)
|
||||
|
||||
ICompiledQuery<Page<PullRequestListItemModel>> query;
|
||||
|
||||
if (address.IsGitHubDotCom())
|
||||
{
|
||||
readPullRequests = new Query()
|
||||
.Repository(Var(nameof(owner)), Var(nameof(name)))
|
||||
.PullRequests(
|
||||
first: 100,
|
||||
after: Var(nameof(after)),
|
||||
orderBy: new IssueOrder { Direction = OrderDirection.Desc, Field = IssueOrderField.CreatedAt },
|
||||
states: Var(nameof(states)))
|
||||
.Select(page => new Page<PullRequestListItemModel>
|
||||
{
|
||||
EndCursor = page.PageInfo.EndCursor,
|
||||
HasNextPage = page.PageInfo.HasNextPage,
|
||||
TotalCount = page.TotalCount,
|
||||
Items = page.Nodes.Select(pr => new ListItemAdapter
|
||||
{
|
||||
Id = pr.Id.Value,
|
||||
Author = new ActorModel
|
||||
{
|
||||
Login = pr.Author.Login,
|
||||
AvatarUrl = pr.Author.AvatarUrl(null),
|
||||
},
|
||||
CommentCount = pr.Comments(0, null, null, null).TotalCount,
|
||||
Number = pr.Number,
|
||||
Reviews = pr.Reviews(null, null, null, null, null, null).AllPages().Select(review => new ReviewAdapter
|
||||
{
|
||||
Body = review.Body,
|
||||
CommentCount = review.Comments(null, null, null, null).TotalCount,
|
||||
}).ToList(),
|
||||
State = (PullRequestStateEnum)pr.State,
|
||||
Title = pr.Title,
|
||||
UpdatedAt = pr.UpdatedAt,
|
||||
}).ToList(),
|
||||
}).Compile();
|
||||
if (readPullRequests == null)
|
||||
{
|
||||
readPullRequests = new Query()
|
||||
.Repository(Var(nameof(owner)), Var(nameof(name)))
|
||||
.PullRequests(
|
||||
first: 100,
|
||||
after: Var(nameof(after)),
|
||||
orderBy: new IssueOrder { Direction = OrderDirection.Desc, Field = IssueOrderField.CreatedAt },
|
||||
states: Var(nameof(states)))
|
||||
.Select(page => new Page<PullRequestListItemModel>
|
||||
{
|
||||
EndCursor = page.PageInfo.EndCursor,
|
||||
HasNextPage = page.PageInfo.HasNextPage,
|
||||
TotalCount = page.TotalCount,
|
||||
Items = page.Nodes.Select(pr => new ListItemAdapter
|
||||
{
|
||||
Id = pr.Id.Value,
|
||||
LastCommit = pr.Commits(null, null, 1, null).Nodes.Select(commit =>
|
||||
new LastCommitSummaryAdapter
|
||||
{
|
||||
CheckSuites = commit.Commit.CheckSuites(null, null, null, null, null).AllPages(10)
|
||||
.Select(suite => new CheckSuiteSummaryModel
|
||||
{
|
||||
CheckRuns = suite.CheckRuns(null, null, null, null, null).AllPages(10)
|
||||
.Select(run => new CheckRunSummaryModel
|
||||
{
|
||||
Conclusion = run.Conclusion.FromGraphQl(),
|
||||
Status = run.Status.FromGraphQl()
|
||||
}).ToList()
|
||||
}).ToList(),
|
||||
Statuses = commit.Commit.Status
|
||||
.Select(context =>
|
||||
context.Contexts.Select(statusContext => new StatusSummaryModel
|
||||
{
|
||||
State = statusContext.State.FromGraphQl(),
|
||||
}).ToList()
|
||||
).SingleOrDefault()
|
||||
}).ToList().FirstOrDefault(),
|
||||
Author = new ActorModel
|
||||
{
|
||||
Login = pr.Author.Login,
|
||||
AvatarUrl = pr.Author.AvatarUrl(null),
|
||||
},
|
||||
CommentCount = pr.Comments(0, null, null, null).TotalCount,
|
||||
Number = pr.Number,
|
||||
Reviews = pr.Reviews(null, null, null, null, null, null).AllPages().Select(review => new ReviewAdapter
|
||||
{
|
||||
Body = review.Body,
|
||||
CommentCount = review.Comments(null, null, null, null).TotalCount,
|
||||
}).ToList(),
|
||||
State = pr.State.FromGraphQl(),
|
||||
Title = pr.Title,
|
||||
UpdatedAt = pr.UpdatedAt,
|
||||
}).ToList(),
|
||||
}).Compile();
|
||||
}
|
||||
|
||||
query = readPullRequests;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (readPullRequestsEnterprise == null)
|
||||
{
|
||||
readPullRequestsEnterprise = new Query()
|
||||
.Repository(Var(nameof(owner)), Var(nameof(name)))
|
||||
.PullRequests(
|
||||
first: 100,
|
||||
after: Var(nameof(after)),
|
||||
orderBy: new IssueOrder { Direction = OrderDirection.Desc, Field = IssueOrderField.CreatedAt },
|
||||
states: Var(nameof(states)))
|
||||
.Select(page => new Page<PullRequestListItemModel>
|
||||
{
|
||||
EndCursor = page.PageInfo.EndCursor,
|
||||
HasNextPage = page.PageInfo.HasNextPage,
|
||||
TotalCount = page.TotalCount,
|
||||
Items = page.Nodes.Select(pr => new ListItemAdapter
|
||||
{
|
||||
Id = pr.Id.Value,
|
||||
LastCommit = pr.Commits(null, null, 1, null).Nodes.Select(commit =>
|
||||
new LastCommitSummaryAdapter
|
||||
{
|
||||
Statuses = commit.Commit.Status
|
||||
.Select(context =>
|
||||
context.Contexts.Select(statusContext => new StatusSummaryModel
|
||||
{
|
||||
State = statusContext.State.FromGraphQl(),
|
||||
}).ToList()
|
||||
).SingleOrDefault()
|
||||
}).ToList().FirstOrDefault(),
|
||||
Author = new ActorModel
|
||||
{
|
||||
Login = pr.Author.Login,
|
||||
AvatarUrl = pr.Author.AvatarUrl(null),
|
||||
},
|
||||
CommentCount = pr.Comments(0, null, null, null).TotalCount,
|
||||
Number = pr.Number,
|
||||
Reviews = pr.Reviews(null, null, null, null, null, null).AllPages().Select(review => new ReviewAdapter
|
||||
{
|
||||
Body = review.Body,
|
||||
CommentCount = review.Comments(null, null, null, null).TotalCount,
|
||||
}).ToList(),
|
||||
State = pr.State.FromGraphQl(),
|
||||
Title = pr.Title,
|
||||
UpdatedAt = pr.UpdatedAt,
|
||||
}).ToList(),
|
||||
}).Compile();
|
||||
}
|
||||
|
||||
query = readPullRequestsEnterprise;
|
||||
}
|
||||
|
||||
var graphql = await graphqlFactory.CreateConnection(address);
|
||||
|
@ -121,12 +207,75 @@ namespace GitHub.Services
|
|||
{ nameof(states), states.Select(x => (PullRequestState)x).ToList() },
|
||||
};
|
||||
|
||||
var result = await graphql.Run(readPullRequests, vars);
|
||||
var result = await graphql.Run(query, vars);
|
||||
|
||||
foreach (ListItemAdapter item in result.Items)
|
||||
foreach (var item in result.Items.Cast<ListItemAdapter>())
|
||||
{
|
||||
item.CommentCount += item.Reviews.Sum(x => x.Count);
|
||||
item.Reviews = null;
|
||||
|
||||
var checkRuns = item.LastCommit?.CheckSuites?.SelectMany(model => model.CheckRuns).ToArray();
|
||||
|
||||
var hasCheckRuns = checkRuns?.Any() ?? false;
|
||||
var hasStatuses = item.LastCommit?.Statuses?.Any() ?? false;
|
||||
|
||||
if (!hasCheckRuns && !hasStatuses)
|
||||
{
|
||||
item.Checks = PullRequestChecksState.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
var checksHasFailure = false;
|
||||
var checksHasCompleteSuccess = true;
|
||||
|
||||
if (hasCheckRuns)
|
||||
{
|
||||
checksHasFailure = checkRuns
|
||||
.Any(model => model.Conclusion.HasValue
|
||||
&& (model.Conclusion.Value == CheckConclusionState.Failure
|
||||
|| model.Conclusion.Value == CheckConclusionState.ActionRequired));
|
||||
|
||||
if (!checksHasFailure)
|
||||
{
|
||||
checksHasCompleteSuccess = checkRuns
|
||||
.All(model => model.Conclusion.HasValue
|
||||
&& (model.Conclusion.Value == CheckConclusionState.Success
|
||||
|| model.Conclusion.Value == CheckConclusionState.Neutral));
|
||||
}
|
||||
}
|
||||
|
||||
var statusHasFailure = false;
|
||||
var statusHasCompleteSuccess = true;
|
||||
|
||||
if (!checksHasFailure && hasStatuses)
|
||||
{
|
||||
statusHasFailure = item.LastCommit
|
||||
.Statuses
|
||||
.Any(status => status.State == StatusState.Failure
|
||||
|| status.State == StatusState.Error);
|
||||
|
||||
if (!statusHasFailure)
|
||||
{
|
||||
statusHasCompleteSuccess =
|
||||
item.LastCommit.Statuses.All(status => status.State == StatusState.Success);
|
||||
}
|
||||
}
|
||||
|
||||
if (checksHasFailure || statusHasFailure)
|
||||
{
|
||||
item.Checks = PullRequestChecksState.Failure;
|
||||
}
|
||||
else if (statusHasCompleteSuccess && checksHasCompleteSuccess)
|
||||
{
|
||||
item.Checks = PullRequestChecksState.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Checks = PullRequestChecksState.Pending;
|
||||
}
|
||||
}
|
||||
|
||||
item.LastCommit = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -392,7 +541,7 @@ namespace GitHub.Services
|
|||
{
|
||||
await gitClient.Checkout(repo, localBranchName);
|
||||
}
|
||||
else if (repository.CloneUrl.Owner == pullRequest.HeadRepositoryOwner)
|
||||
else if (string.Equals(repository.CloneUrl.Owner, pullRequest.HeadRepositoryOwner, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var remote = await gitClient.GetHttpRemote(repo, "origin");
|
||||
await gitClient.Fetch(repo, remote.Name);
|
||||
|
@ -524,7 +673,7 @@ namespace GitHub.Services
|
|||
|
||||
public bool IsPullRequestFromRepository(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest)
|
||||
{
|
||||
return pullRequest.HeadRepositoryOwner == repository.CloneUrl.Owner;
|
||||
return string.Equals(repository.CloneUrl?.Owner, pullRequest.HeadRepositoryOwner, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public IObservable<Unit> SwitchToBranch(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest)
|
||||
|
@ -666,6 +815,16 @@ namespace GitHub.Services
|
|||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ConfirmCancelPendingReview()
|
||||
{
|
||||
return MessageBox.Show(
|
||||
GitHub.App.Resources.CancelPendingReviewConfirmation,
|
||||
GitHub.App.Resources.CancelPendingReviewConfirmationCaption,
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question) == DialogResult.Yes;
|
||||
}
|
||||
|
||||
async Task<string> CreateRemote(IRepository repo, UriString cloneUri)
|
||||
{
|
||||
foreach (var remote in repo.Network.Remotes)
|
||||
|
@ -840,6 +999,8 @@ namespace GitHub.Services
|
|||
class ListItemAdapter : PullRequestListItemModel
|
||||
{
|
||||
public IList<ReviewAdapter> Reviews { get; set; }
|
||||
|
||||
public LastCommitSummaryAdapter LastCommit { get; set; }
|
||||
}
|
||||
|
||||
class ReviewAdapter
|
||||
|
@ -848,5 +1009,28 @@ namespace GitHub.Services
|
|||
public int CommentCount { get; set; }
|
||||
public int Count => CommentCount + (!string.IsNullOrWhiteSpace(Body) ? 1 : 0);
|
||||
}
|
||||
|
||||
class LastCommitSummaryAdapter
|
||||
{
|
||||
public List<CheckSuiteSummaryModel> CheckSuites { get; set; }
|
||||
|
||||
public List<StatusSummaryModel> Statuses { get; set; }
|
||||
}
|
||||
|
||||
class CheckSuiteSummaryModel
|
||||
{
|
||||
public List<CheckRunSummaryModel> CheckRuns { get; set; }
|
||||
}
|
||||
|
||||
class CheckRunSummaryModel
|
||||
{
|
||||
public CheckConclusionState? Conclusion { get; set; }
|
||||
public CheckStatusState Status { get; set; }
|
||||
}
|
||||
|
||||
class StatusSummaryModel
|
||||
{
|
||||
public StatusState State { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,8 +138,8 @@ namespace GitHub.Services
|
|||
{ ErrorType.EnterpriseConnectFailed, Map(Defaults("Connecting to GitHub Enterprise instance failed", "Could not find a GitHub Enterprise instance at '{0}'. Double check the URL and your internet/intranet connection.")) },
|
||||
{ ErrorType.LaunchEnterpriseConnectionFailed, Map(Defaults("Failed to launch the enterprise connection.")) },
|
||||
{ ErrorType.LogFileError, Map(Defaults("Could not open the log file", "Could not find or open the log file.")) },
|
||||
{ ErrorType.LoginFailed, Map(Defaults("login failed", "Unable to retrieve your user info from the server. A proxy server might be interfering with the request.")) },
|
||||
{ ErrorType.LogoutFailed, Map(Defaults("logout failed", "Logout failed. A proxy server might be interfering with the request.")) },
|
||||
{ ErrorType.LoginFailed, Map(Defaults("Login failed", "Unable to retrieve your user info from the server. A proxy server might be interfering with the request.")) },
|
||||
{ ErrorType.LogoutFailed, Map(Defaults("Logout failed", "Logout failed. A proxy server might be interfering with the request.")) },
|
||||
{ ErrorType.RepoCreationAsPrivateNotAvailableForFreePlan, Map(Defaults("Failed to create private repository", "You are currently on a free plan and unable to create private repositories. Either make the repository public or upgrade your account on the website to a plan that allows for private repositories.")) },
|
||||
{ ErrorType.RepoCreationFailed, Map(Defaults("Failed to create repository", "An error occurred while creating the repository. You might need to open a shell and debug the state of this repo.")) },
|
||||
{ ErrorType.RepoExistsOnDisk, Map(Defaults("Failed to create repository", "A repository named '{0}' exists in the directory\n'{1}'.")) },
|
||||
|
@ -209,7 +209,7 @@ namespace GitHub.Services
|
|||
return userError.Throw();
|
||||
}
|
||||
|
||||
static UserError GetUserFriendlyError(this Exception exception, ErrorType errorType, params object[] messageArgs)
|
||||
public static UserError GetUserFriendlyError(this Exception exception, ErrorType errorType, params object[] messageArgs)
|
||||
{
|
||||
return Translator.Value.GetUserError(errorType, exception, messageArgs);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
using System;
|
||||
using System.Windows.Media.Imaging;
|
||||
using GitHub.Logging;
|
||||
using GitHub.Models;
|
||||
using GitHub.Primitives;
|
||||
using GitHub.Services;
|
||||
using Serilog;
|
||||
|
||||
namespace GitHub.ViewModels
|
||||
{
|
||||
public class ActorViewModel : ViewModelBase, IActorViewModel
|
||||
{
|
||||
const string DefaultAvatar = "pack://application:,,,/GitHub.App;component/Images/default_user_avatar.png";
|
||||
static readonly ILogger log = LogManager.ForContext<ActorViewModel>();
|
||||
|
||||
public ActorViewModel()
|
||||
{
|
||||
|
@ -16,10 +20,33 @@ namespace GitHub.ViewModels
|
|||
public ActorViewModel(ActorModel model)
|
||||
{
|
||||
Login = model?.Login ?? "[unknown]";
|
||||
Avatar = model?.AvatarUrl != null ?
|
||||
new BitmapImage(new Uri(model.AvatarUrl)) :
|
||||
AvatarProvider.CreateBitmapImage(DefaultAvatar);
|
||||
AvatarUrl = model?.AvatarUrl ?? DefaultAvatar;
|
||||
|
||||
if (model?.AvatarUrl != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var uri = new Uri(model.AvatarUrl);
|
||||
|
||||
// Image requests to enterprise hosts over https always fail currently,
|
||||
// so just display the default avatar. See #1547.
|
||||
if (uri.Scheme != "https" ||
|
||||
uri.Host.EndsWith("githubusercontent.com", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
AvatarUrl = model.AvatarUrl;
|
||||
Avatar = new BitmapImage(uri);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error(ex, "Invalid avatar URL");
|
||||
}
|
||||
}
|
||||
|
||||
if (AvatarUrl == null)
|
||||
{
|
||||
Avatar = AvatarProvider.CreateBitmapImage(DefaultAvatar);
|
||||
AvatarUrl = DefaultAvatar;
|
||||
}
|
||||
}
|
||||
|
||||
public BitmapSource Avatar { get; set; }
|
||||
|
|
|
@ -65,7 +65,7 @@ namespace GitHub.ViewModels.Dialog
|
|||
this.WhenAnyValue(x => x.EnterpriseUrl, x => x.EnterpriseUrlValidator.ValidationResult)
|
||||
.Throttle(TimeSpan.FromMilliseconds(500), scheduler)
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Subscribe(x => EnterpriseUrlChanged(x.Item1, x.Item2?.IsValid ?? false));
|
||||
.Subscribe(x => UpdatingProbeStatus = EnterpriseUrlChanged(x.Item1, x.Item2?.IsValid ?? false));
|
||||
|
||||
NavigateLearnMore = ReactiveCommand.CreateAsyncObservable(_ =>
|
||||
{
|
||||
|
@ -122,13 +122,19 @@ namespace GitHub.ViewModels.Dialog
|
|||
get;
|
||||
}
|
||||
|
||||
public Task UpdatingProbeStatus
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
protected override async Task ResetValidation()
|
||||
{
|
||||
EnterpriseUrl = null;
|
||||
await EnterpriseUrlValidator.ResetAsync();
|
||||
}
|
||||
|
||||
async void EnterpriseUrlChanged(string url, bool valid)
|
||||
async Task EnterpriseUrlChanged(string url, bool valid)
|
||||
{
|
||||
if (!valid)
|
||||
{
|
||||
|
|
|
@ -44,6 +44,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
readonly ILoggedOutViewModel loggedOut;
|
||||
readonly INotAGitHubRepositoryViewModel notAGitHubRepository;
|
||||
readonly INotAGitRepositoryViewModel notAGitRepository;
|
||||
readonly ILoginFailedViewModel loginFailed;
|
||||
readonly SemaphoreSlim navigating = new SemaphoreSlim(1);
|
||||
readonly ObservableAsPropertyHelper<ContentOverride> contentOverride;
|
||||
readonly ObservableAsPropertyHelper<bool> isSearchEnabled;
|
||||
|
@ -52,6 +53,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
readonly ReactiveCommand<Unit> showPullRequests;
|
||||
readonly ReactiveCommand<object> openInBrowser;
|
||||
readonly ReactiveCommand<object> help;
|
||||
IDisposable connectionSubscription;
|
||||
Task initializeTask;
|
||||
IViewModel content;
|
||||
ILocalRepositoryModel localRepository;
|
||||
|
@ -68,7 +70,8 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
INavigationViewModel navigator,
|
||||
ILoggedOutViewModel loggedOut,
|
||||
INotAGitHubRepositoryViewModel notAGitHubRepository,
|
||||
INotAGitRepositoryViewModel notAGitRepository)
|
||||
INotAGitRepositoryViewModel notAGitRepository,
|
||||
ILoginFailedViewModel loginFailed)
|
||||
{
|
||||
Guard.ArgumentNotNull(viewModelFactory, nameof(viewModelFactory));
|
||||
Guard.ArgumentNotNull(apiClientFactory, nameof(apiClientFactory));
|
||||
|
@ -80,6 +83,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
Guard.ArgumentNotNull(loggedOut, nameof(loggedOut));
|
||||
Guard.ArgumentNotNull(notAGitHubRepository, nameof(notAGitHubRepository));
|
||||
Guard.ArgumentNotNull(notAGitRepository, nameof(notAGitRepository));
|
||||
Guard.ArgumentNotNull(loginFailed, nameof(loginFailed));
|
||||
|
||||
this.viewModelFactory = viewModelFactory;
|
||||
this.apiClientFactory = apiClientFactory;
|
||||
|
@ -89,6 +93,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
this.loggedOut = loggedOut;
|
||||
this.notAGitHubRepository = notAGitHubRepository;
|
||||
this.notAGitRepository = notAGitRepository;
|
||||
this.loginFailed = loginFailed;
|
||||
|
||||
var contentAndNavigatorContent = Observable.CombineLatest(
|
||||
this.WhenAnyValue(x => x.Content),
|
||||
|
@ -389,6 +394,8 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
log.Debug("UpdateContent called with {CloneUrl}", repository?.CloneUrl);
|
||||
|
||||
LocalRepository = repository;
|
||||
connectionSubscription?.Dispose();
|
||||
connectionSubscription = null;
|
||||
Connection = null;
|
||||
Content = null;
|
||||
navigator.Clear();
|
||||
|
@ -410,18 +417,47 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
var isDotCom = HostAddress.IsGitHubDotComUri(repositoryUrl);
|
||||
var client = await apiClientFactory.Create(repository.CloneUrl);
|
||||
var isEnterprise = isDotCom ? false : await client.IsEnterprise();
|
||||
var notGitHubRepo = true;
|
||||
|
||||
if ((isDotCom || isEnterprise) && await IsValidRepository(client))
|
||||
if (isDotCom || isEnterprise)
|
||||
{
|
||||
var hostAddress = HostAddress.Create(repository.CloneUrl);
|
||||
|
||||
notGitHubRepo = false;
|
||||
|
||||
Connection = await connectionManager.GetConnection(hostAddress);
|
||||
Connection?.WhenAnyValue(
|
||||
x => x.IsLoggedIn,
|
||||
x => x.IsLoggingIn,
|
||||
(_, __) => Unit.Default)
|
||||
.Skip(1)
|
||||
.Throttle(TimeSpan.FromMilliseconds(100))
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Subscribe(_ => UpdateContent(LocalRepository).Forget());
|
||||
|
||||
if (Connection?.IsLoggedIn == true)
|
||||
{
|
||||
log.Debug("Found a GitHub repository: {CloneUrl}", repository?.CloneUrl);
|
||||
Content = navigator;
|
||||
await ShowDefaultPage();
|
||||
if (await IsValidRepository(client) == true)
|
||||
{
|
||||
log.Debug("Found a GitHub repository: {CloneUrl}", repository?.CloneUrl);
|
||||
Content = navigator;
|
||||
await ShowDefaultPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
notGitHubRepo = true;
|
||||
}
|
||||
}
|
||||
else if (Connection?.IsLoggingIn == true)
|
||||
{
|
||||
log.Debug("Found a GitHub repository: {CloneUrl} and logging in", repository?.CloneUrl);
|
||||
Content = null;
|
||||
}
|
||||
else if (Connection?.ConnectionError != null)
|
||||
{
|
||||
log.Debug("Found a GitHub repository: {CloneUrl} with login error", repository?.CloneUrl);
|
||||
loginFailed.Initialize(Connection.ConnectionError.GetUserFriendlyError(ErrorType.LoginFailed));
|
||||
Content = loginFailed;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -429,7 +465,8 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
Content = loggedOut;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if (notGitHubRepo)
|
||||
{
|
||||
log.Debug("Not a GitHub repository: {CloneUrl}", repository?.CloneUrl);
|
||||
Content = notAGitHubRepository;
|
||||
|
|
|
@ -7,6 +7,7 @@ using System.Reactive;
|
|||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
using GitHub.Collections;
|
||||
using GitHub.Extensions;
|
||||
using GitHub.Extensions.Reactive;
|
||||
|
@ -209,6 +210,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
this.WhenAnyValue(x => x.SelectedState),
|
||||
this.WhenAnyValue(x => x.AuthorFilter.Selected),
|
||||
(loading, count, _, __, ___) => Tuple.Create(loading, count))
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Subscribe(x => UpdateState(x.Item1, x.Item2)));
|
||||
dispose.Add(
|
||||
Observable.FromEventPattern<ErrorEventArgs>(
|
||||
|
@ -246,10 +248,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
{
|
||||
numberFilter = 0;
|
||||
|
||||
if (SearchQuery.StartsWith('#'))
|
||||
{
|
||||
int.TryParse(SearchQuery.Substring(1), out numberFilter);
|
||||
}
|
||||
int.TryParse(SearchQuery.Substring(SearchQuery.StartsWith('#') ? 1 : 0), out numberFilter);
|
||||
|
||||
if (numberFilter == 0)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using GitHub.Services;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace GitHub.ViewModels.GitHubPane
|
||||
{
|
||||
/// <summary>
|
||||
/// The view model for the "Login Failed" view in the GitHub pane.
|
||||
/// </summary>
|
||||
[Export(typeof(ILoginFailedViewModel))]
|
||||
[PartCreationPolicy(CreationPolicy.NonShared)]
|
||||
public class LoginFailedViewModel : PanePageViewModelBase, ILoginFailedViewModel
|
||||
{
|
||||
readonly ITeamExplorerServices teServices;
|
||||
UserError loginError;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoginFailedViewModel"/> class.
|
||||
/// </summary>
|
||||
[ImportingConstructor]
|
||||
public LoginFailedViewModel(ITeamExplorerServices teServices)
|
||||
{
|
||||
this.teServices = teServices;
|
||||
OpenTeamExplorer = ReactiveCommand.Create().OnExecuteCompleted(_ => DoOpenTeamExplorer());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public UserError LoginError
|
||||
{
|
||||
get => loginError;
|
||||
private set => this.RaiseAndSetIfChanged(ref loginError, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReactiveCommand<object> OpenTeamExplorer { get; }
|
||||
|
||||
public void Initialize(UserError error)
|
||||
{
|
||||
LoginError = error;
|
||||
}
|
||||
|
||||
void DoOpenTeamExplorer() => teServices.ShowConnectPage();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace GitHub.ViewModels.GitHubPane
|
||||
{
|
||||
public enum PullRequestCheckType
|
||||
{
|
||||
StatusApi,
|
||||
ChecksApi
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Media.Imaging;
|
||||
using GitHub.Extensions;
|
||||
using GitHub.Factories;
|
||||
using GitHub.Models;
|
||||
using GitHub.Services;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace GitHub.ViewModels.GitHubPane
|
||||
{
|
||||
[Export(typeof(IPullRequestCheckViewModel))]
|
||||
[PartCreationPolicy(CreationPolicy.NonShared)]
|
||||
public class PullRequestCheckViewModel: ViewModelBase, IPullRequestCheckViewModel
|
||||
{
|
||||
private readonly IUsageTracker usageTracker;
|
||||
const string DefaultAvatar = "pack://application:,,,/GitHub.App;component/Images/default_user_avatar.png";
|
||||
|
||||
public static IEnumerable<IPullRequestCheckViewModel> Build(IViewViewModelFactory viewViewModelFactory, PullRequestDetailModel pullRequest)
|
||||
{
|
||||
var statuses = pullRequest.Statuses?.Select(model =>
|
||||
{
|
||||
PullRequestCheckStatus checkStatus;
|
||||
switch (model.State)
|
||||
{
|
||||
case StatusState.Expected:
|
||||
case StatusState.Error:
|
||||
case StatusState.Failure:
|
||||
checkStatus = PullRequestCheckStatus.Failure;
|
||||
break;
|
||||
case StatusState.Pending:
|
||||
checkStatus = PullRequestCheckStatus.Pending;
|
||||
break;
|
||||
case StatusState.Success:
|
||||
checkStatus = PullRequestCheckStatus.Success;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Unkown PullRequestCheckStatusEnum");
|
||||
}
|
||||
|
||||
var pullRequestCheckViewModel = (PullRequestCheckViewModel) viewViewModelFactory.CreateViewModel<IPullRequestCheckViewModel>();
|
||||
pullRequestCheckViewModel.CheckType = PullRequestCheckType.StatusApi;
|
||||
pullRequestCheckViewModel.Title = model.Context;
|
||||
pullRequestCheckViewModel.Description = model.Description;
|
||||
pullRequestCheckViewModel.Status = checkStatus;
|
||||
pullRequestCheckViewModel.DetailsUrl = !string.IsNullOrEmpty(model.TargetUrl) ? new Uri(model.TargetUrl) : null;
|
||||
|
||||
return pullRequestCheckViewModel;
|
||||
}) ?? new PullRequestCheckViewModel[0];
|
||||
|
||||
var checks = pullRequest.CheckSuites?.SelectMany(model => model.CheckRuns)
|
||||
.Select(model =>
|
||||
{
|
||||
PullRequestCheckStatus checkStatus;
|
||||
switch (model.Status)
|
||||
{
|
||||
case CheckStatusState.Requested:
|
||||
case CheckStatusState.Queued:
|
||||
case CheckStatusState.InProgress:
|
||||
checkStatus = PullRequestCheckStatus.Pending;
|
||||
break;
|
||||
|
||||
case CheckStatusState.Completed:
|
||||
switch (model.Conclusion)
|
||||
{
|
||||
case CheckConclusionState.Success:
|
||||
checkStatus = PullRequestCheckStatus.Success;
|
||||
break;
|
||||
|
||||
case CheckConclusionState.ActionRequired:
|
||||
case CheckConclusionState.TimedOut:
|
||||
case CheckConclusionState.Cancelled:
|
||||
case CheckConclusionState.Failure:
|
||||
case CheckConclusionState.Neutral:
|
||||
checkStatus = PullRequestCheckStatus.Failure;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
var pullRequestCheckViewModel = (PullRequestCheckViewModel)viewViewModelFactory.CreateViewModel<IPullRequestCheckViewModel>();
|
||||
pullRequestCheckViewModel.CheckType = PullRequestCheckType.ChecksApi;
|
||||
pullRequestCheckViewModel.Title = model.Name;
|
||||
pullRequestCheckViewModel.Description = model.Summary;
|
||||
pullRequestCheckViewModel.Status = checkStatus;
|
||||
pullRequestCheckViewModel.DetailsUrl = new Uri(model.DetailsUrl);
|
||||
|
||||
return pullRequestCheckViewModel;
|
||||
}) ?? new PullRequestCheckViewModel[0];
|
||||
|
||||
return statuses.Concat(checks).OrderBy(model => model.Title);
|
||||
}
|
||||
|
||||
[ImportingConstructor]
|
||||
public PullRequestCheckViewModel(IUsageTracker usageTracker)
|
||||
{
|
||||
this.usageTracker = usageTracker;
|
||||
OpenDetailsUrl = ReactiveCommand.Create().OnExecuteCompleted(DoOpenDetailsUrl);
|
||||
}
|
||||
|
||||
private void DoOpenDetailsUrl(object obj)
|
||||
{
|
||||
Expression<Func<UsageModel.MeasuresModel, int>> expression;
|
||||
if (CheckType == PullRequestCheckType.StatusApi)
|
||||
{
|
||||
expression = x => x.NumberOfPRStatusesOpenInGitHub;
|
||||
}
|
||||
else
|
||||
{
|
||||
expression = x => x.NumberOfPRChecksOpenInGitHub;
|
||||
}
|
||||
|
||||
usageTracker.IncrementCounter(expression).Forget();
|
||||
}
|
||||
|
||||
public string Title { get; private set; }
|
||||
|
||||
public string Description { get; private set; }
|
||||
|
||||
public PullRequestCheckType CheckType { get; private set; }
|
||||
|
||||
public PullRequestCheckStatus Status{ get; private set; }
|
||||
|
||||
public Uri DetailsUrl { get; private set; }
|
||||
|
||||
public ReactiveCommand<object> OpenDetailsUrl { get; }
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
readonly IUsageTracker usageTracker;
|
||||
readonly ITeamExplorerContext teamExplorerContext;
|
||||
readonly ISyncSubmodulesCommand syncSubmodulesCommand;
|
||||
readonly IViewViewModelFactory viewViewModelFactory;
|
||||
IModelService modelService;
|
||||
PullRequestDetailModel model;
|
||||
IActorViewModel author;
|
||||
|
@ -55,16 +56,18 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
bool refreshOnActivate;
|
||||
Uri webUrl;
|
||||
IDisposable sessionSubscription;
|
||||
IReadOnlyList<IPullRequestCheckViewModel> checks;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PullRequestDetailViewModel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="localRepository">The local repository.</param>
|
||||
/// <param name="modelService">The model service.</param>
|
||||
/// <param name="pullRequestsService">The pull requests service.</param>
|
||||
/// <param name="sessionManager">The pull request session manager.</param>
|
||||
/// <param name="modelServiceFactory">The model service factory</param>
|
||||
/// <param name="usageTracker">The usage tracker.</param>
|
||||
/// <param name="teamExplorerContext">The context for tracking repo changes</param>
|
||||
/// <param name="files">The view model which will display the changed files</param>
|
||||
/// <param name="syncSubmodulesCommand">A command that will be run when <see cref="SyncSubmodules"/> is executed</param>
|
||||
[ImportingConstructor]
|
||||
public PullRequestDetailViewModel(
|
||||
IPullRequestService pullRequestsService,
|
||||
|
@ -73,7 +76,8 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
IUsageTracker usageTracker,
|
||||
ITeamExplorerContext teamExplorerContext,
|
||||
IPullRequestFilesViewModel files,
|
||||
ISyncSubmodulesCommand syncSubmodulesCommand)
|
||||
ISyncSubmodulesCommand syncSubmodulesCommand,
|
||||
IViewViewModelFactory viewViewModelFactory)
|
||||
{
|
||||
Guard.ArgumentNotNull(pullRequestsService, nameof(pullRequestsService));
|
||||
Guard.ArgumentNotNull(sessionManager, nameof(sessionManager));
|
||||
|
@ -81,6 +85,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
Guard.ArgumentNotNull(usageTracker, nameof(usageTracker));
|
||||
Guard.ArgumentNotNull(teamExplorerContext, nameof(teamExplorerContext));
|
||||
Guard.ArgumentNotNull(syncSubmodulesCommand, nameof(syncSubmodulesCommand));
|
||||
Guard.ArgumentNotNull(viewViewModelFactory, nameof(viewViewModelFactory));
|
||||
|
||||
this.pullRequestsService = pullRequestsService;
|
||||
this.sessionManager = sessionManager;
|
||||
|
@ -88,6 +93,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
this.usageTracker = usageTracker;
|
||||
this.teamExplorerContext = teamExplorerContext;
|
||||
this.syncSubmodulesCommand = syncSubmodulesCommand;
|
||||
this.viewViewModelFactory = viewViewModelFactory;
|
||||
Files = files;
|
||||
|
||||
Checkout = ReactiveCommand.CreateAsyncObservable(
|
||||
|
@ -120,10 +126,16 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
SyncSubmodules.Subscribe(_ => Refresh().ToObservable());
|
||||
SubscribeOperationError(SyncSubmodules);
|
||||
|
||||
OpenOnGitHub = ReactiveCommand.Create();
|
||||
OpenOnGitHub = ReactiveCommand.Create().OnExecuteCompleted(DoOpenDetailsUrl);
|
||||
|
||||
ShowReview = ReactiveCommand.Create().OnExecuteCompleted(DoShowReview);
|
||||
}
|
||||
|
||||
private void DoOpenDetailsUrl(object obj)
|
||||
{
|
||||
usageTracker.IncrementCounter(measuresModel => measuresModel.NumberOfPRDetailsOpenInGitHub).Forget();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying pull request model.
|
||||
/// </summary>
|
||||
|
@ -195,6 +207,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
private set { this.RaiseAndSetIfChanged(ref targetBranchDisplayName, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the pull request branch is checked out.
|
||||
/// </summary>
|
||||
public bool IsCheckedOut
|
||||
|
@ -302,6 +315,12 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
/// </summary>
|
||||
public ReactiveCommand<object> ShowReview { get; }
|
||||
|
||||
public IReadOnlyList<IPullRequestCheckViewModel> Checks
|
||||
{
|
||||
get { return checks; }
|
||||
private set { this.RaiseAndSetIfChanged(ref checks, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the view model.
|
||||
/// </summary>
|
||||
|
@ -377,6 +396,8 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
Body = !string.IsNullOrWhiteSpace(pullRequest.Body) ? pullRequest.Body : Resources.NoDescriptionProvidedMarkdown;
|
||||
Reviews = PullRequestReviewSummaryViewModel.BuildByUser(Session.User, pullRequest).ToList();
|
||||
|
||||
Checks = PullRequestCheckViewModel.Build(viewViewModelFactory, pullRequest)?.ToList();
|
||||
|
||||
await Files.InitializeAsync(Session);
|
||||
|
||||
var localBranches = await pullRequestsService.GetLocalBranches(LocalRepository, pullRequest).ToList();
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PullRequestDirectoryNode"/> class.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the directory, relative to the repository.</param>
|
||||
/// <param name="relativePath">The path to the directory, relative to the repository.</param>
|
||||
public PullRequestDirectoryNode(string relativePath)
|
||||
{
|
||||
DirectoryName = System.IO.Path.GetFileName(relativePath);
|
||||
|
|
|
@ -7,7 +7,9 @@ using System.Reactive;
|
|||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Reactive.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using GitHub.Extensions;
|
||||
using GitHub.Models;
|
||||
using GitHub.Services;
|
||||
|
@ -50,7 +52,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
DiffFileWithWorkingDirectory = ReactiveCommand.CreateAsyncTask(
|
||||
isBranchCheckedOut,
|
||||
x => (Task)editorService.OpenDiff(pullRequestSession, ((IPullRequestFileNode)x).RelativePath));
|
||||
OpenFileInWorkingDirectory = ReactiveCommand.CreateAsyncTask(
|
||||
OpenFileInWorkingDirectory = new NonDeletedFileCommand(
|
||||
isBranchCheckedOut,
|
||||
x => (Task)editorService.OpenFile(pullRequestSession, ((IPullRequestFileNode)x).RelativePath, true));
|
||||
|
||||
|
@ -198,5 +200,37 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
|
||||
return threads.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the <see cref="OpenFileInWorkingDirectory"/> command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We need to "Open File in Solution" when the parameter passed to the command parameter
|
||||
/// represents a deleted file. ReactiveCommand doesn't allow us to change the CanExecute
|
||||
/// state depending on the parameter, so we override
|
||||
/// <see cref="ICommand.CanExecute(object)"/> to do this ourselves.
|
||||
/// </remarks>
|
||||
class NonDeletedFileCommand : ReactiveCommand<Unit>, ICommand
|
||||
{
|
||||
public NonDeletedFileCommand(
|
||||
IObservable<bool> canExecute,
|
||||
Func<object, Task> executeAsync)
|
||||
: base(canExecute, x => executeAsync(x).ToObservable())
|
||||
{
|
||||
}
|
||||
|
||||
bool ICommand.CanExecute(object parameter)
|
||||
{
|
||||
if (parameter is IPullRequestFileNode node)
|
||||
{
|
||||
if (node.Status == PullRequestFileStatus.Removed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return CanExecute(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
{
|
||||
Id = model.Id;
|
||||
Author = new ActorViewModel(model.Author);
|
||||
Checks = model.Checks;
|
||||
CommentCount = model.CommentCount;
|
||||
Number = model.Number;
|
||||
Title = model.Title;
|
||||
|
@ -26,13 +27,16 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Id { get; protected set; }
|
||||
public string Id { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IActorViewModel Author { get; protected set; }
|
||||
public IActorViewModel Author { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int CommentCount { get; protected set; }
|
||||
public PullRequestChecksState Checks { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int CommentCount { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsCurrent
|
||||
|
@ -42,12 +46,12 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Number { get; protected set; }
|
||||
public int Number { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Title { get; protected set; }
|
||||
public string Title { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DateTimeOffset UpdatedAt { get; protected set; }
|
||||
public DateTimeOffset UpdatedAt { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
|
||||
readonly IPullRequestEditorService editorService;
|
||||
readonly IPullRequestSessionManager sessionManager;
|
||||
readonly IPullRequestService pullRequestService;
|
||||
IPullRequestSession session;
|
||||
IDisposable sessionSubscription;
|
||||
PullRequestReviewModel model;
|
||||
|
@ -35,6 +36,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
|
||||
[ImportingConstructor]
|
||||
public PullRequestReviewAuthoringViewModel(
|
||||
IPullRequestService pullRequestService,
|
||||
IPullRequestEditorService editorService,
|
||||
IPullRequestSessionManager sessionManager,
|
||||
IPullRequestFilesViewModel files)
|
||||
|
@ -43,6 +45,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
Guard.ArgumentNotNull(sessionManager, nameof(sessionManager));
|
||||
Guard.ArgumentNotNull(files, nameof(files));
|
||||
|
||||
this.pullRequestService = pullRequestService;
|
||||
this.editorService = editorService;
|
||||
this.sessionManager = sessionManager;
|
||||
|
||||
|
@ -68,7 +71,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
_ => DoSubmit(Octokit.PullRequestReviewEvent.RequestChanges));
|
||||
Cancel = ReactiveCommand.CreateAsyncTask(DoCancel);
|
||||
NavigateToPullRequest = ReactiveCommand.Create().OnExecuteCompleted(_ =>
|
||||
NavigateTo(Invariant($"{LocalRepository.Owner}/{LocalRepository.Name}/pull/{PullRequestModel.Number}")));
|
||||
NavigateTo(Invariant($"{RemoteRepositoryOwner}/{LocalRepository.Name}/pull/{PullRequestModel.Number}")));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -271,10 +274,17 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
{
|
||||
if (Model?.Id != null)
|
||||
{
|
||||
await session.CancelReview();
|
||||
if (pullRequestService.ConfirmCancelPendingReview())
|
||||
{
|
||||
await session.CancelReview();
|
||||
Close();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
{
|
||||
var existing = new Dictionary<string, PullRequestReviewSummaryViewModel>();
|
||||
|
||||
foreach (var review in pullRequest.Reviews.OrderBy(x => x.Id))
|
||||
foreach (var review in pullRequest.Reviews.OrderBy(x => x.SubmittedAt))
|
||||
{
|
||||
if (review.State == PullRequestReviewState.Pending && review.Author.Login != currentUser.Login)
|
||||
continue;
|
||||
|
@ -90,6 +90,7 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
{
|
||||
case PullRequestReviewState.Approved:
|
||||
case PullRequestReviewState.ChangesRequested:
|
||||
case PullRequestReviewState.Dismissed:
|
||||
return 1;
|
||||
case PullRequestReviewState.Pending:
|
||||
return 2;
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<package id="Microsoft.VisualStudio.TextManager.Interop.8.0" version="8.0.50728" targetFramework="net461" />
|
||||
<package id="Microsoft.VisualStudio.Utilities" version="14.3.25407" targetFramework="net461" />
|
||||
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" />
|
||||
<package id="Octokit.GraphQL" version="0.1.0-beta" targetFramework="net461" />
|
||||
<package id="Octokit.GraphQL" version="0.1.1-beta" targetFramework="net461" />
|
||||
<package id="Rothko" version="0.0.3-ghfvs" targetFramework="net461" />
|
||||
<package id="Rx-Core" version="2.2.5-custom" targetFramework="net45" />
|
||||
<package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net45" />
|
||||
|
|
|
@ -203,7 +203,9 @@
|
|||
<Compile Include="ViewModels\GitHubPane\IIssueListViewModelBase.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\ILoggedOutViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\INavigationViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\ILoginFailedViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\IPanePageViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\IPullRequestCheckViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\IPullRequestFilesViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\IPullRequestListItemViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\IPullRequestListViewModel.cs" />
|
||||
|
|
|
@ -110,6 +110,7 @@ namespace GitHub.Services
|
|||
/// </returns>
|
||||
Task<ContentChanges> CompareWith(IRepository repository, string sha1, string sha2, string path, byte[] contents);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of a configuration key.
|
||||
/// </summary>
|
||||
/// <param name="repository">The repository.</param>
|
||||
|
@ -189,7 +190,7 @@ namespace GitHub.Services
|
|||
/// <summary>
|
||||
/// Find the merge base SHA between two commits.
|
||||
/// </summary>
|
||||
/// <param name="repository">The repository.</param>
|
||||
/// <param name="repo">The repository.</param>
|
||||
/// <param name="targetCloneUrl">The clone url of the PR target repo.</param>
|
||||
/// <param name="baseSha">The PR base SHA.</param>
|
||||
/// <param name="headSha">The PR head SHA.</param>
|
||||
|
@ -201,9 +202,10 @@ namespace GitHub.Services
|
|||
/// <exception cref="LibGit2Sharp.NotFoundException">Thrown when the merge base can't be found.</exception>
|
||||
Task<string> GetPullRequestMergeBase(IRepository repo, UriString targetCloneUrl, string baseSha, string headSha, string baseRef, int pullNumber);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the current head is pushed to its remote tracking branch.
|
||||
/// </summary>
|
||||
/// <param name="repository">The repository.</param>
|
||||
/// <param name="repo">The repository.</param>
|
||||
/// <returns></returns>
|
||||
Task<bool> IsHeadPushed(IRepository repo);
|
||||
|
||||
|
@ -212,7 +214,7 @@ namespace GitHub.Services
|
|||
/// <paramref name="baseBranch"/> and <paramref name="compareBranch"/> and returns their
|
||||
/// commit messages.
|
||||
/// </summary>
|
||||
/// <param name="repository">The repository.</param>
|
||||
/// <param name="repo">The repository.</param>
|
||||
/// <param name="baseBranch">The base branch to find a merge base with.</param>
|
||||
/// <param name="compareBranch">The compare branch to find a merge base with.</param>
|
||||
/// <param name="maxCommits">The maximum number of commits to return.</param>
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace GitHub.Services
|
|||
/// <param name="owner">The repository owner.</param>
|
||||
/// <param name="name">The repository name.</param>
|
||||
/// <param name="after">The end cursor of the previous page, or null for the first page.</param>
|
||||
/// <param name="states">The pull request states to filter by</param>
|
||||
/// <returns>A page of pull request item models.</returns>
|
||||
Task<Page<PullRequestListItemModel>> ReadPullRequests(
|
||||
HostAddress address,
|
||||
|
@ -84,6 +85,7 @@ namespace GitHub.Services
|
|||
/// Sync submodules on the current branch.
|
||||
/// </summary>
|
||||
/// <param name="repository">The repository.</param>
|
||||
/// <param name="progress">A method that will be called with progress messages</param>
|
||||
Task<bool> SyncSubmodules(ILocalRepositoryModel repository, Action<string> progress);
|
||||
|
||||
/// <summary>
|
||||
|
@ -224,5 +226,11 @@ namespace GitHub.Services
|
|||
string baseBranch,
|
||||
string compareBranch,
|
||||
int maxCommits);
|
||||
|
||||
/// <summary>
|
||||
/// Displays a confirmation diaglog to ask if the user wants to cancel a pending review.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool ConfirmCancelPendingReview();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -153,6 +153,7 @@ namespace GitHub.Services
|
|||
|
||||
/// <summary>
|
||||
/// Refreshes the pull request session.
|
||||
/// </summary>
|
||||
/// <returns>A task which completes when the session has completed refreshing.</returns>
|
||||
Task Refresh();
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace GitHub.Services
|
|||
/// If the currently checked out branch represents a pull request then <see cref="CurrentSession"/>
|
||||
/// will return an <see cref="IPullRequestSession"/> containing the details of that pull request.
|
||||
/// A session for any other pull request can also be retrieved by calling
|
||||
/// <see cref="GetSession(IPullRequestModel)"/>.
|
||||
/// <see cref="GetSession(string, string, int)"/>.
|
||||
///
|
||||
/// Calling <see cref="GetLiveFile(string, ITextView, ITextBuffer)"/> will return an
|
||||
/// <see cref="IPullRequestSessionFile"/> which tracks both the contents of a text buffer and the
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace GitHub.Services
|
|||
/// <param name="repositoryPath">The directory that will contain the repository directory.</param>
|
||||
/// <param name="progress">
|
||||
/// An object through which to report progress. This must be of type
|
||||
/// <see cref="System.IProgress{Microsoft.VisualStudio.Shell.ServiceProgressData}"/>, but
|
||||
/// System.IProgress<Microsoft.VisualStudio.Shell.ServiceProgressData>, but
|
||||
/// as that type is only available in VS2017+ it is typed as <see cref="object"/> here.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
|
|
|
@ -35,6 +35,7 @@ namespace GitHub.Services
|
|||
/// The value returned by the <paramref name="viewModel"/>'s
|
||||
/// <see cref="IDialogContentViewModel.Done"/> observable, or null if the dialog was
|
||||
/// canceled.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// The first existing connection will be used. If there is no existing connection, the
|
||||
/// login dialog will be shown first.
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
using ReactiveUI;
|
||||
|
||||
namespace GitHub.ViewModels.GitHubPane
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the view model for the "Login Failed" view in the GitHub pane.
|
||||
/// </summary>
|
||||
public interface ILoginFailedViewModel : IPanePageViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a description of the login failure.
|
||||
/// </summary>
|
||||
UserError LoginError { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a command which opens the Team Explorer Connect page.
|
||||
/// </summary>
|
||||
ReactiveCommand<object> OpenTeamExplorer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the view model with an error.
|
||||
/// </summary>
|
||||
/// <param name="error">The error.</param>
|
||||
void Initialize(UserError error);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using System.Windows.Media.Imaging;
|
||||
using GitHub.Models;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace GitHub.ViewModels.GitHubPane
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a view model for displaying details of a pull request Status or Check.
|
||||
/// </summary>
|
||||
public interface IPullRequestCheckViewModel: IViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The title of the Status/Check
|
||||
/// </summary>
|
||||
string Title { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The description of the Status/Check
|
||||
/// </summary>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The status of the Status/Check
|
||||
/// </summary>
|
||||
PullRequestCheckStatus Status { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The url where more information about the Status/Check can be found
|
||||
/// </summary>
|
||||
Uri DetailsUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A command that opens the DetailsUrl in a browser
|
||||
/// </summary>
|
||||
|
||||
ReactiveCommand<object> OpenDetailsUrl { get; }
|
||||
}
|
||||
|
||||
public enum PullRequestCheckStatus
|
||||
{
|
||||
Pending,
|
||||
Success,
|
||||
Failure
|
||||
}
|
||||
}
|
|
@ -175,6 +175,11 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
/// </summary>
|
||||
ReactiveCommand<object> ShowReview { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest pull request Checks & Statuses
|
||||
/// </summary>
|
||||
IReadOnlyList<IPullRequestCheckViewModel> Checks { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the view model.
|
||||
/// </summary>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using GitHub.Models;
|
||||
|
||||
namespace GitHub.ViewModels.GitHubPane
|
||||
{
|
||||
|
@ -27,5 +28,10 @@ namespace GitHub.ViewModels.GitHubPane
|
|||
/// Gets the last updated time of the pull request.
|
||||
/// </summary>
|
||||
DateTimeOffset UpdatedAt { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pull request checks and statuses summary
|
||||
/// </summary>
|
||||
PullRequestChecksState Checks { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace GitHub.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a GitHub Enterprise gist from the currently selected text.
|
||||
/// </summary>
|
||||
public interface ICreateGistEnterpriseCommand : IVsCommand
|
||||
{
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ namespace GitHub.Factories
|
|||
/// <summary>
|
||||
/// Creates a view model based on the specified interface type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The view model interface type.</typeparam>
|
||||
/// <typeparam name="TViewModel">The view model interface type.</typeparam>
|
||||
/// <returns>The view model.</returns>
|
||||
TViewModel CreateViewModel<TViewModel>() where TViewModel : IViewModel;
|
||||
|
||||
|
|
|
@ -149,6 +149,7 @@
|
|||
<ItemGroup>
|
||||
<Compile Include="Commands\ICopyLinkCommand.cs" />
|
||||
<Compile Include="Commands\IBlameLinkCommand.cs" />
|
||||
<Compile Include="Commands\ICreateGistEnterpriseCommand.cs" />
|
||||
<Compile Include="Commands\IOpenFromClipboardCommand.cs" />
|
||||
<Compile Include="Commands\IOpenFromUrlCommand.cs" />
|
||||
<Compile Include="Commands\IToggleInlineCommentMarginCommand.cs" />
|
||||
|
@ -170,6 +171,12 @@
|
|||
<Compile Include="Extensions\ConnectionManagerExtensions.cs" />
|
||||
<Compile Include="GitHubLogicException.cs" />
|
||||
<Compile Include="Models\ActorModel.cs" />
|
||||
<Compile Include="Models\AnnotationModel.cs" />
|
||||
<Compile Include="Models\CheckAnnotationLevel.cs" />
|
||||
<Compile Include="Models\CheckConclusionState.cs" />
|
||||
<Compile Include="Models\CheckRunModel.cs" />
|
||||
<Compile Include="Models\CheckStatusState.cs" />
|
||||
<Compile Include="Models\CheckSuiteModel.cs" />
|
||||
<Compile Include="Models\CommitMessage.cs" />
|
||||
<Compile Include="Models\DiffChangeType.cs" />
|
||||
<Compile Include="Models\DiffChunk.cs" />
|
||||
|
@ -185,6 +192,8 @@
|
|||
<Compile Include="Models\PullRequestReviewCommentModel.cs" />
|
||||
<Compile Include="Models\PullRequestReviewModel.cs" />
|
||||
<Compile Include="Models\PullRequestReviewThreadModel.cs" />
|
||||
<Compile Include="Models\StatusModel.cs" />
|
||||
<Compile Include="Models\StatusState.cs" />
|
||||
<Compile Include="Services\GitHubContext.cs" />
|
||||
<Compile Include="Services\IEnterpriseCapabilitiesService.cs" />
|
||||
<Compile Include="Services\IGitHubContextService.cs" />
|
||||
|
@ -197,6 +206,7 @@
|
|||
<Compile Include="Services\IUsageService.cs" />
|
||||
<Compile Include="Settings\PkgCmdID.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\IGitHubPaneViewModel.cs" />
|
||||
<Compile Include="ViewModels\GitHubPane\IGitHubToolWindowManager.cs" />
|
||||
<Compile Include="ViewModels\IConnectionInitializedViewModel.cs" />
|
||||
<Compile Include="ViewModels\IInfoPanel.cs" />
|
||||
<Compile Include="ViewModels\IViewModel.cs" />
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
namespace GitHub.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Model for a single check annotation.
|
||||
/// </summary>
|
||||
public class CheckRunAnnotationModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The path to the file that this annotation was made on.
|
||||
/// </summary>
|
||||
public string BlobUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The starting line number (1 indexed).
|
||||
/// </summary>
|
||||
public int StartLine { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ending line number (1 indexed).
|
||||
/// </summary>
|
||||
public int EndLine { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The path that this annotation was made on.
|
||||
/// </summary>
|
||||
public string Filename { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The annotation's message.
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The annotation's title.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The annotation's severity level.
|
||||
/// </summary>
|
||||
public CheckAnnotationLevel? AnnotationLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional information about the annotation.
|
||||
/// </summary>
|
||||
public string RawDetails { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace GitHub.Models
|
||||
{
|
||||
public enum CheckAnnotationLevel
|
||||
{
|
||||
Failure,
|
||||
Notice,
|
||||
Warning,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
namespace GitHub.Models
|
||||
{
|
||||
public enum CheckConclusionState
|
||||
{
|
||||
ActionRequired,
|
||||
TimedOut,
|
||||
Cancelled,
|
||||
Failure,
|
||||
Success,
|
||||
Neutral,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GitHub.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Model for a single check run.
|
||||
/// </summary>
|
||||
public class CheckRunModel
|
||||
{
|
||||
/// <summary>The conclusion of the check run.</summary>
|
||||
public CheckConclusionState? Conclusion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current status of a Check Run.
|
||||
/// </summary>
|
||||
public CheckStatusState Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the date and time when the check run was completed.
|
||||
/// </summary>
|
||||
public DateTimeOffset? CompletedAt { get; set; }
|
||||
|
||||
/// <summary>The name of the check for this check run.</summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL from which to find full details of the check run on the integrator's site.
|
||||
/// </summary>
|
||||
public string DetailsUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The summary of a Check Run.
|
||||
/// </summary>
|
||||
public string Summary { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace GitHub.Models
|
||||
{
|
||||
public enum CheckStatusState
|
||||
{
|
||||
Queued,
|
||||
InProgress,
|
||||
Completed,
|
||||
Requested,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GitHub.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Model for a single check suite.
|
||||
/// </summary>
|
||||
public class CheckSuiteModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The check runs associated with a check suite.
|
||||
/// </summary>
|
||||
public List<CheckRunModel> CheckRuns { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Primitives;
|
||||
using Octokit;
|
||||
|
||||
|
@ -7,7 +9,7 @@ namespace GitHub.Models
|
|||
/// <summary>
|
||||
/// Represents a configured connection to a GitHub account.
|
||||
/// </summary>
|
||||
public interface IConnection
|
||||
public interface IConnection : INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the host address of the GitHub instance.
|
||||
|
@ -32,6 +34,11 @@ namespace GitHub.Models
|
|||
/// </summary>
|
||||
bool IsLoggedIn { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a login is currently being attempted on the connection.
|
||||
/// </summary>
|
||||
bool IsLoggingIn { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the exception that occurred when trying to log in, if <see cref="IsLoggedIn"/> is
|
||||
/// false.
|
||||
|
|
|
@ -29,6 +29,7 @@ namespace GitHub.Models
|
|||
/// Generates a http(s) url to the repository in the remote server, optionally
|
||||
/// pointing to a specific file and specific line range in it.
|
||||
/// </summary>
|
||||
/// <param name="linkType">The type of repository link to create</param>
|
||||
/// <param name="path">The file to generate an url to. Optional.</param>
|
||||
/// <param name="startLine">A specific line, or (if specifying the <paramref name="endLine"/> as well) the start of a range</param>
|
||||
/// <param name="endLine">The end of a line range on the specified file.</param>
|
||||
|
|
|
@ -13,6 +13,14 @@ namespace GitHub.Models
|
|||
Merged,
|
||||
}
|
||||
|
||||
public enum PullRequestChecksState
|
||||
{
|
||||
None,
|
||||
Pending,
|
||||
Success,
|
||||
Failure
|
||||
}
|
||||
|
||||
public interface IPullRequestModel : ICopyable<IPullRequestModel>,
|
||||
IEquatable<IPullRequestModel>, IComparable<IPullRequestModel>
|
||||
{
|
||||
|
|
|
@ -91,5 +91,15 @@ namespace GitHub.Models
|
|||
/// into threads, as such each pull request review comment will appear in both collections.
|
||||
/// </remarks>
|
||||
public IReadOnlyList<PullRequestReviewThreadModel> Threads { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a collection of pull request Checks Suites
|
||||
/// </summary>
|
||||
public IReadOnlyList<CheckSuiteModel> CheckSuites { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a collection of pull request Statuses
|
||||
/// </summary>
|
||||
public IReadOnlyList<StatusModel> Statuses { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,11 @@ namespace GitHub.Models
|
|||
/// </summary>
|
||||
public PullRequestStateEnum State { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pull request checks and statuses summary
|
||||
/// </summary>
|
||||
public PullRequestChecksState Checks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date/time at which the pull request was last updated.
|
||||
/// </summary>
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
namespace GitHub.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Model for a single pull request Status.
|
||||
/// </summary>
|
||||
public class StatusModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The state of the Status
|
||||
/// </summary>
|
||||
public StatusState State { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Status context or title
|
||||
/// </summary>
|
||||
public string Context { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The url where more information about the Status can be found
|
||||
/// </summary>
|
||||
public string TargetUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The descritption for the Status
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace GitHub.Models
|
||||
{
|
||||
public enum StatusState
|
||||
{
|
||||
Expected,
|
||||
Error,
|
||||
Failure,
|
||||
Pending,
|
||||
Success,
|
||||
}
|
||||
}
|
|
@ -58,6 +58,9 @@ namespace GitHub.Models
|
|||
public int NumberOfWelcomeDocsClicks { get; set; }
|
||||
public int NumberOfWelcomeTrainingClicks { get; set; }
|
||||
public int NumberOfGitHubPaneHelpClicks { get; set; }
|
||||
public int NumberOfPRDetailsOpenInGitHub { get; set; }
|
||||
public int NumberOfPRStatusesOpenInGitHub { get; set; }
|
||||
public int NumberOfPRChecksOpenInGitHub { get; set; }
|
||||
public int NumberOfPRDetailsViewChanges { get; set; }
|
||||
public int NumberOfPRDetailsViewFile { get; set; }
|
||||
public int NumberOfPRDetailsCompareWithSolution { get; set; }
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GitHub.Models;
|
||||
using GitHub.Primitives;
|
||||
|
||||
|
@ -9,31 +11,102 @@ namespace GitHub.Services
|
|||
/// </summary>
|
||||
public class Connection : IConnection
|
||||
{
|
||||
public Connection(
|
||||
HostAddress hostAddress,
|
||||
string userName,
|
||||
Octokit.User user,
|
||||
Exception connectionError)
|
||||
string username;
|
||||
Octokit.User user;
|
||||
bool isLoggedIn;
|
||||
bool isLoggingIn;
|
||||
Exception connectionError;
|
||||
|
||||
public Connection(HostAddress hostAddress)
|
||||
{
|
||||
HostAddress = hostAddress;
|
||||
Username = userName;
|
||||
User = user;
|
||||
ConnectionError = connectionError;
|
||||
isLoggedIn = false;
|
||||
isLoggingIn = true;
|
||||
}
|
||||
|
||||
public Connection(
|
||||
HostAddress hostAddress,
|
||||
string username,
|
||||
Octokit.User user)
|
||||
{
|
||||
HostAddress = hostAddress;
|
||||
this.username = username;
|
||||
this.user = user;
|
||||
isLoggedIn = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public HostAddress HostAddress { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Username { get; }
|
||||
public string Username
|
||||
{
|
||||
get => username;
|
||||
private set => RaiseAndSetIfChanged(ref username, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Octokit.User User { get; }
|
||||
public Octokit.User User
|
||||
{
|
||||
get => user;
|
||||
private set => RaiseAndSetIfChanged(ref user, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsLoggedIn => ConnectionError == null;
|
||||
public bool IsLoggedIn
|
||||
{
|
||||
get => isLoggedIn;
|
||||
private set => RaiseAndSetIfChanged(ref isLoggedIn, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Exception ConnectionError { get; }
|
||||
public bool IsLoggingIn
|
||||
{
|
||||
get => isLoggingIn;
|
||||
private set => RaiseAndSetIfChanged(ref isLoggingIn, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Exception ConnectionError
|
||||
{
|
||||
get => connectionError;
|
||||
private set => RaiseAndSetIfChanged(ref connectionError, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public void SetLoggingIn()
|
||||
{
|
||||
ConnectionError = null;
|
||||
IsLoggedIn = false;
|
||||
IsLoggingIn = true;
|
||||
User = null;
|
||||
Username = null;
|
||||
}
|
||||
|
||||
public void SetError(Exception e)
|
||||
{
|
||||
ConnectionError = e;
|
||||
IsLoggingIn = false;
|
||||
IsLoggedIn = false;
|
||||
}
|
||||
|
||||
public void SetSuccess(Octokit.User user)
|
||||
{
|
||||
User = user;
|
||||
Username = user.Login;
|
||||
IsLoggingIn = false;
|
||||
IsLoggedIn = true;
|
||||
}
|
||||
|
||||
void RaiseAndSetIfChanged<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
|
||||
{
|
||||
if (!Equals(field, value))
|
||||
{
|
||||
field = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,5 +92,12 @@ namespace GitHub.Services
|
|||
/// <param name="address"></param>
|
||||
/// <returns>A task tracking the operation.</returns>
|
||||
Task LogOut(HostAddress address);
|
||||
|
||||
/// <summary>
|
||||
/// Retries logging in to a failed connection.
|
||||
/// </summary>
|
||||
/// <param name="connection">The connection.</param>
|
||||
/// <returns>The resulting connection.</returns>
|
||||
Task Retry(IConnection connection);
|
||||
}
|
||||
}
|
|
@ -39,7 +39,11 @@ namespace GitHub.Services
|
|||
/// <summary>
|
||||
/// Shows the Create Gist dialog.
|
||||
/// </summary>
|
||||
Task ShowCreateGist();
|
||||
/// <param name="connection">
|
||||
/// The connection to use. If null, the first connection will be used, or the user promted
|
||||
/// to log in if there are no connections.
|
||||
/// </param>
|
||||
Task ShowCreateGist(IConnection connection);
|
||||
|
||||
/// <summary>
|
||||
/// Shows the Create Repository dialog.
|
||||
|
|
|
@ -30,14 +30,14 @@ namespace GitHub.Services
|
|||
/// <summary>
|
||||
/// Convert a context to a repository URL.
|
||||
/// </summary>
|
||||
/// <param name="context">The context to convert.</param>
|
||||
/// <param name="windowTitle">A browser window title.</param>
|
||||
/// <returns>A repository URL</returns>
|
||||
GitHubContext FindContextFromWindowTitle(string windowTitle);
|
||||
|
||||
/// <summary>
|
||||
/// Find a context from a browser window title.
|
||||
/// </summary>
|
||||
/// <param name="windowTitle">A browser window title.</param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns>The context or null if none can be found</returns>
|
||||
Uri ToRepositoryUrl(GitHubContext context);
|
||||
|
||||
|
@ -71,6 +71,30 @@ namespace GitHub.Services
|
|||
/// <returns>The resolved commit-ish, blob path and commit SHA for the blob. Path will be null if the commit-ish can be resolved but not the blob.</returns>
|
||||
(string commitish, string path, string commitSha) ResolveBlob(string repositoryDir, GitHubContext context, string remoteName = "origin");
|
||||
|
||||
/// <summary>
|
||||
/// Find the object-ish (first 8 chars of a blob SHA) from the path to historical blob created by Team Explorer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Team Explorer creates temporary blob files in the following format:
|
||||
/// C:\Users\me\AppData\Local\Temp\TFSTemp\vctmp21996_181282.IOpenFromClipboardCommand.783ac965.cs
|
||||
/// The object-ish appears immediately before the file extension and the path contains the folder "TFSTemp".
|
||||
/// </remarks>
|
||||
/// <param name="tempFile">The path to a possible Team Explorer temporary blob file.</param>
|
||||
/// <returns>The target file's object-ish (blob SHA fragment) or null if the path isn't recognized as a Team Explorer blob file.</returns>
|
||||
string FindObjectishForTFSTempFile(string tempFile);
|
||||
|
||||
/// <summary>
|
||||
/// Find a tree entry in the commit log where a blob appears and return its commit SHA and path.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Search back through the commit log for the first tree entry where a blob appears. This operation only takes
|
||||
/// a fraction of a seond on the `github/VisualStudio` repository even if a tree entry casn't be found.
|
||||
/// </remarks>
|
||||
/// <param name="repositoryDir">The target repository directory.</param>
|
||||
/// <param name="objectish">The fragment of a blob SHA to find.</param>
|
||||
/// <returns>The commit SHA and blob path or null if the blob can't be found.</returns>
|
||||
(string commitSha, string blobPath) ResolveBlobFromHistory(string repositoryDir, string objectish);
|
||||
|
||||
/// <summary>
|
||||
/// Check if a file in the working directory has changed since a specified commit-ish.
|
||||
/// </summary>
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace GitHub.Services
|
|||
/// is null or no remote exists, this method returns null
|
||||
/// </summary>
|
||||
/// <param name="repository">The repository to look at for the remote.</param>
|
||||
/// <param name="remote">The remote name to look for</param>
|
||||
/// <returns>A <see cref="UriString"/> representing the origin or null if none found.</returns>
|
||||
UriString GetUri(IRepository repository, string remote = "origin");
|
||||
|
||||
|
@ -24,6 +25,7 @@ namespace GitHub.Services
|
|||
/// walks up the parent directories until it either finds a repository, or reaches the root disk.
|
||||
/// </remarks>
|
||||
/// <param name="path">The path to start probing</param>
|
||||
/// <param name="remote">The remote name to look for</param>
|
||||
/// <returns>A <see cref="UriString"/> representing the origin or null if none found.</returns>
|
||||
UriString GetUri(string path, string remote = "origin");
|
||||
|
||||
|
@ -42,7 +44,8 @@ namespace GitHub.Services
|
|||
/// <summary>
|
||||
/// Returns a <see cref="UriString"/> representing the uri of a remote.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="repo">The repository to look at for the remote.</param>
|
||||
/// <param name="remote">The remote name to look for</param>
|
||||
/// <returns></returns>
|
||||
UriString GetRemoteUri(IRepository repo, string remote = "origin");
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace GitHub.Services
|
|||
{
|
||||
public interface ITeamExplorerServices : INotificationService
|
||||
{
|
||||
void ShowConnectPage();
|
||||
void ShowPublishSection();
|
||||
void ClearNotifications();
|
||||
}
|
||||
|
|
|
@ -17,9 +17,11 @@ namespace GitHub.Services
|
|||
/// <param name="recurseSubmodules">Whether to recursively clone submodules.</param>
|
||||
/// <param name="progress">
|
||||
/// An object through which to report progress. This must be of type
|
||||
/// <see cref="System.IProgress{Microsoft.VisualStudio.Shell.ServiceProgressData}"/>, but
|
||||
/// System.IProgress<Microsoft.VisualStudio.Shell.ServiceProgressData>, but
|
||||
/// as that type is only available in VS2017+ it is typed as <see cref="object"/> here.
|
||||
/// </param>
|
||||
/// <seealso cref="System.IProgress{T}"/>
|
||||
/// <seealso cref="Microsoft.VisualStudio.Shell.ServiceProgressData"/>
|
||||
Task Clone(
|
||||
string cloneUrl,
|
||||
string clonePath,
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace GitHub.Services
|
|||
/// Our workaround is to create, open and delete a solution in the repo directory.
|
||||
/// This triggers an event that causes the target repo to open. ;)
|
||||
/// </remarks>
|
||||
/// <param name="repoPath">The path to the repository to open</param>
|
||||
/// <param name="directory">The path to the repository to open</param>
|
||||
/// <returns>True if a transient solution was successfully created in target directory (which should trigger opening of repository).</returns>
|
||||
bool TryOpenRepository(string directory);
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ namespace GitHub.VisualStudio
|
|||
public const int refreshCommand = 0x302;
|
||||
public const int pullRequestCommand = 0x310;
|
||||
public const int createGistCommand = 0x400;
|
||||
public const int createGistEnterpriseCommand = 0x401;
|
||||
public const int openLinkCommand = 0x100;
|
||||
public const int copyLinkCommand = 0x101;
|
||||
public const int goToSolutionOrPullRequestFileCommand = 0x102;
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace GitHub.ViewModels.GitHubPane
|
||||
{
|
||||
/// <summary>
|
||||
/// The Visual Studio service interface for accessing the GitHub Pane.
|
||||
/// </summary>
|
||||
[Guid("FC9EC5B5-C297-4548-A229-F8E16365543C")]
|
||||
[ComVisible(true)]
|
||||
public interface IGitHubToolWindowManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensure that the GitHub pane is created and visible.
|
||||
/// </summary>
|
||||
/// <returns>The view model for the GitHub Pane.</returns>
|
||||
Task<IGitHubPaneViewModel> ShowGitHubPane();
|
||||
}
|
||||
}
|
|
@ -8,8 +8,14 @@ namespace GitHub.VisualStudio.TeamExplorer.Connect
|
|||
void DoCreate();
|
||||
void SignOut();
|
||||
void Login();
|
||||
void Retry();
|
||||
bool OpenRepository();
|
||||
string ErrorMessage { get; }
|
||||
IConnection SectionConnection { get; }
|
||||
bool IsLoggingIn { get; }
|
||||
bool ShowLogin { get; }
|
||||
bool ShowLogout { get; }
|
||||
bool ShowRetry { get; }
|
||||
ICommand Clone { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ namespace GitHub.InlineReviews.Commands
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InlineCommentNavigationCommand"/> class.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider"></param>
|
||||
/// <param name="tagAggregatorFactory">The tag aggregator factory.</param>
|
||||
/// <param name="peekService">The peek service.</param>
|
||||
/// <param name="commandSet">The GUID of the group the command belongs to.</param>
|
||||
|
@ -74,7 +75,7 @@ namespace GitHub.InlineReviews.Commands
|
|||
/// <summary>
|
||||
/// Gets the text buffer position for the specified line.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The parameters.</param>
|
||||
/// <param name="textView">The text view containing the buffer</param>
|
||||
/// <param name="lineNumber">The 0-based line number.</param>
|
||||
/// <returns></returns>
|
||||
protected int GetCursorPoint(ITextView textView, int lineNumber)
|
||||
|
@ -217,7 +218,10 @@ namespace GitHub.InlineReviews.Commands
|
|||
/// <summary>
|
||||
/// Shows the inline comments for the specified tag in a peek view.
|
||||
/// </summary>
|
||||
/// <param name="tag"></param>
|
||||
/// <param name="textView">The text view containing the tag</param>
|
||||
/// <param name="tag">The inline comment tag</param>
|
||||
/// <param name="parameter">The navigation parameter detailing a search from the specified tag</param>
|
||||
/// <param name="allTextViews">The full list of text views</param>
|
||||
protected void ShowPeekComments(
|
||||
InlineCommentNavigationParams parameter,
|
||||
ITextView textView,
|
||||
|
|
|
@ -84,6 +84,8 @@
|
|||
<Compile Include="Margins\InlineCommentMargin.cs" />
|
||||
<Compile Include="Margins\InlineCommentMarginVisible.cs" />
|
||||
<Compile Include="Margins\InlineCommentMarginEnabled.cs" />
|
||||
<Compile Include="Services\CommentService.cs" />
|
||||
<Compile Include="Services\ICommentService.cs" />
|
||||
<Compile Include="PullRequestStatusBarPackage.cs" />
|
||||
<Compile Include="InlineReviewsPackage.cs" />
|
||||
<Compile Include="Models\InlineCommentThreadModel.cs" />
|
||||
|
@ -370,11 +372,11 @@
|
|||
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Octokit.GraphQL, Version=0.1.0.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Octokit.GraphQL.0.1.0-beta\lib\netstandard1.1\Octokit.GraphQL.dll</HintPath>
|
||||
<Reference Include="Octokit.GraphQL, Version=0.1.1.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Octokit.GraphQL.Core, Version=0.1.0.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Octokit.GraphQL.0.1.0-beta\lib\netstandard1.1\Octokit.GraphQL.Core.dll</HintPath>
|
||||
<Reference Include="Octokit.GraphQL.Core, Version=0.1.1.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
using Microsoft.VisualStudio.Utilities;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.Text.Tagging;
|
||||
using Microsoft.VisualStudio.Text.Classification;
|
||||
using GitHub.InlineReviews.Services;
|
||||
using GitHub.Services;
|
||||
using GitHub.VisualStudio;
|
||||
using GitHub.InlineReviews.Services;
|
||||
|
||||
namespace GitHub.InlineReviews.Margins
|
||||
{
|
||||
|
@ -17,28 +19,41 @@ namespace GitHub.InlineReviews.Margins
|
|||
[TextViewRole(PredefinedTextViewRoles.Interactive)]
|
||||
internal sealed class InlineCommentMarginProvider : IWpfTextViewMarginProvider
|
||||
{
|
||||
readonly IEditorFormatMapService editorFormatMapService;
|
||||
readonly IViewTagAggregatorFactoryService tagAggregatorFactory;
|
||||
readonly IInlineCommentPeekService peekService;
|
||||
readonly Lazy<IEditorFormatMapService> editorFormatMapService;
|
||||
readonly Lazy<IViewTagAggregatorFactoryService> tagAggregatorFactory;
|
||||
readonly Lazy<IInlineCommentPeekService> peekService;
|
||||
readonly Lazy<IPullRequestSessionManager> sessionManager;
|
||||
readonly UIContext uiContext;
|
||||
|
||||
[ImportingConstructor]
|
||||
public InlineCommentMarginProvider(
|
||||
IGitHubServiceProvider serviceProvider,
|
||||
IEditorFormatMapService editorFormatMapService,
|
||||
IViewTagAggregatorFactoryService tagAggregatorFactory,
|
||||
IInlineCommentPeekService peekService)
|
||||
Lazy<IPullRequestSessionManager> sessionManager,
|
||||
Lazy<IEditorFormatMapService> editorFormatMapService,
|
||||
Lazy<IViewTagAggregatorFactoryService> tagAggregatorFactory,
|
||||
Lazy<IInlineCommentPeekService> peekService)
|
||||
{
|
||||
this.sessionManager = sessionManager;
|
||||
this.editorFormatMapService = editorFormatMapService;
|
||||
this.tagAggregatorFactory = tagAggregatorFactory;
|
||||
this.peekService = peekService;
|
||||
sessionManager = new Lazy<IPullRequestSessionManager>(() => serviceProvider.GetService<IPullRequestSessionManager>());
|
||||
|
||||
uiContext = UIContext.FromUIContextGuid(new Guid(Guids.UIContext_Git));
|
||||
}
|
||||
|
||||
public IWpfTextViewMargin CreateMargin(IWpfTextViewHost wpfTextViewHost, IWpfTextViewMargin parent)
|
||||
{
|
||||
if (!uiContext.IsActive)
|
||||
{
|
||||
// Only create margin when in the context of a Git repository
|
||||
return null;
|
||||
}
|
||||
|
||||
return new InlineCommentMargin(
|
||||
wpfTextViewHost, peekService, editorFormatMapService, tagAggregatorFactory, sessionManager);
|
||||
wpfTextViewHost,
|
||||
peekService.Value,
|
||||
editorFormatMapService.Value,
|
||||
tagAggregatorFactory.Value,
|
||||
sessionManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ using System.ComponentModel.Composition;
|
|||
using GitHub.Commands;
|
||||
using GitHub.Services;
|
||||
using GitHub.Settings;
|
||||
using GitHub.VisualStudio;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
using Microsoft.VisualStudio.Utilities;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
|
||||
|
@ -19,18 +21,19 @@ namespace GitHub.InlineReviews.Margins
|
|||
[TextViewRole(PredefinedTextViewRoles.Editable)]
|
||||
internal sealed class PullRequestFileMarginProvider : IWpfTextViewMarginProvider
|
||||
{
|
||||
readonly IPullRequestSessionManager sessionManager;
|
||||
readonly IToggleInlineCommentMarginCommand enableInlineCommentsCommand;
|
||||
readonly IGoToSolutionOrPullRequestFileCommand goToSolutionOrPullRequestFileCommand;
|
||||
readonly IPackageSettings packageSettings;
|
||||
readonly Lazy<IPullRequestSessionManager> sessionManager;
|
||||
readonly Lazy<IToggleInlineCommentMarginCommand> enableInlineCommentsCommand;
|
||||
readonly Lazy<IGoToSolutionOrPullRequestFileCommand> goToSolutionOrPullRequestFileCommand;
|
||||
readonly Lazy<IPackageSettings> packageSettings;
|
||||
readonly Lazy<IUsageTracker> usageTracker;
|
||||
readonly UIContext uiContext;
|
||||
|
||||
[ImportingConstructor]
|
||||
public PullRequestFileMarginProvider(
|
||||
IToggleInlineCommentMarginCommand enableInlineCommentsCommand,
|
||||
IGoToSolutionOrPullRequestFileCommand goToSolutionOrPullRequestFileCommand,
|
||||
IPullRequestSessionManager sessionManager,
|
||||
IPackageSettings packageSettings,
|
||||
Lazy<IToggleInlineCommentMarginCommand> enableInlineCommentsCommand,
|
||||
Lazy<IGoToSolutionOrPullRequestFileCommand> goToSolutionOrPullRequestFileCommand,
|
||||
Lazy<IPullRequestSessionManager> sessionManager,
|
||||
Lazy<IPackageSettings> packageSettings,
|
||||
Lazy<IUsageTracker> usageTracker)
|
||||
{
|
||||
this.enableInlineCommentsCommand = enableInlineCommentsCommand;
|
||||
|
@ -38,6 +41,8 @@ namespace GitHub.InlineReviews.Margins
|
|||
this.sessionManager = sessionManager;
|
||||
this.packageSettings = packageSettings;
|
||||
this.usageTracker = usageTracker;
|
||||
|
||||
uiContext = UIContext.FromUIContextGuid(new Guid(Guids.UIContext_Git));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -50,8 +55,14 @@ namespace GitHub.InlineReviews.Margins
|
|||
/// </returns>
|
||||
public IWpfTextViewMargin CreateMargin(IWpfTextViewHost wpfTextViewHost, IWpfTextViewMargin marginContainer)
|
||||
{
|
||||
if (!uiContext.IsActive)
|
||||
{
|
||||
// Only create margin when in the context of a Git repository
|
||||
return null;
|
||||
}
|
||||
|
||||
// Comments in the editor feature flag
|
||||
if (!packageSettings.EditorComments)
|
||||
if (!packageSettings.Value.EditorComments)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -63,7 +74,11 @@ namespace GitHub.InlineReviews.Margins
|
|||
}
|
||||
|
||||
return new PullRequestFileMargin(
|
||||
wpfTextViewHost.TextView, enableInlineCommentsCommand, goToSolutionOrPullRequestFileCommand, sessionManager, usageTracker);
|
||||
wpfTextViewHost.TextView,
|
||||
enableInlineCommentsCommand.Value,
|
||||
goToSolutionOrPullRequestFileCommand.Value,
|
||||
sessionManager.Value,
|
||||
usageTracker);
|
||||
}
|
||||
|
||||
bool IsDiffView(ITextView textView) => textView.Roles.Contains("DIFF");
|
||||
|
|
|
@ -20,12 +20,10 @@ namespace GitHub.InlineReviews.Models
|
|||
/// </summary>
|
||||
/// <param name="relativePath">The relative path to the file that the thread is on.</param>
|
||||
/// <param name="commitSha">The SHA of the commit that the thread appears on.</param>
|
||||
/// <param name="originalPosition">
|
||||
/// The 1-based line number in the original diff that the thread was left on.
|
||||
/// </param>
|
||||
/// <param name="diffMatch">
|
||||
/// The last five lines of the thread's diff hunk, in reverse order.
|
||||
/// </param>
|
||||
/// <param name="comments">The comments in the thread</param>
|
||||
public InlineCommentThreadModel(
|
||||
string relativePath,
|
||||
string commitSha,
|
||||
|
|
|
@ -16,17 +16,19 @@ namespace GitHub.InlineReviews.Peek
|
|||
readonly IPullRequestSessionManager sessionManager;
|
||||
readonly INextInlineCommentCommand nextCommentCommand;
|
||||
readonly IPreviousInlineCommentCommand previousCommentCommand;
|
||||
readonly ICommentService commentService;
|
||||
|
||||
public InlineCommentPeekableItemSource(
|
||||
IInlineCommentPeekService peekService,
|
||||
public InlineCommentPeekableItemSource(IInlineCommentPeekService peekService,
|
||||
IPullRequestSessionManager sessionManager,
|
||||
INextInlineCommentCommand nextCommentCommand,
|
||||
IPreviousInlineCommentCommand previousCommentCommand)
|
||||
IPreviousInlineCommentCommand previousCommentCommand,
|
||||
ICommentService commentService)
|
||||
{
|
||||
this.peekService = peekService;
|
||||
this.sessionManager = sessionManager;
|
||||
this.nextCommentCommand = nextCommentCommand;
|
||||
this.previousCommentCommand = previousCommentCommand;
|
||||
this.commentService = commentService;
|
||||
}
|
||||
|
||||
public void AugmentPeekSession(IPeekSession session, IList<IPeekableItem> peekableItems)
|
||||
|
@ -38,7 +40,8 @@ namespace GitHub.InlineReviews.Peek
|
|||
session,
|
||||
sessionManager,
|
||||
nextCommentCommand,
|
||||
previousCommentCommand);
|
||||
previousCommentCommand,
|
||||
commentService);
|
||||
viewModel.Initialize().Forget();
|
||||
peekableItems.Add(new InlineCommentPeekableItem(viewModel));
|
||||
}
|
||||
|
|
|
@ -20,18 +20,21 @@ namespace GitHub.InlineReviews.Peek
|
|||
readonly IPullRequestSessionManager sessionManager;
|
||||
readonly INextInlineCommentCommand nextCommentCommand;
|
||||
readonly IPreviousInlineCommentCommand previousCommentCommand;
|
||||
readonly ICommentService commentService;
|
||||
|
||||
[ImportingConstructor]
|
||||
public InlineCommentPeekableItemSourceProvider(
|
||||
IInlineCommentPeekService peekService,
|
||||
IPullRequestSessionManager sessionManager,
|
||||
INextInlineCommentCommand nextCommentCommand,
|
||||
IPreviousInlineCommentCommand previousCommentCommand)
|
||||
IPreviousInlineCommentCommand previousCommentCommand,
|
||||
ICommentService commentService)
|
||||
{
|
||||
this.peekService = peekService;
|
||||
this.sessionManager = sessionManager;
|
||||
this.nextCommentCommand = nextCommentCommand;
|
||||
this.previousCommentCommand = previousCommentCommand;
|
||||
this.commentService = commentService;
|
||||
}
|
||||
|
||||
public IPeekableItemSource TryCreatePeekableItemSource(ITextBuffer textBuffer)
|
||||
|
@ -40,7 +43,8 @@ namespace GitHub.InlineReviews.Peek
|
|||
peekService,
|
||||
sessionManager,
|
||||
nextCommentCommand,
|
||||
previousCommentCommand);
|
||||
previousCommentCommand,
|
||||
commentService);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
using System.ComponentModel.Composition;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace GitHub.InlineReviews.Services
|
||||
{
|
||||
[Export(typeof(ICommentService))]
|
||||
[PartCreationPolicy(CreationPolicy.NonShared)]
|
||||
public class CommentService:ICommentService
|
||||
{
|
||||
public bool ConfirmCommentDelete()
|
||||
{
|
||||
return MessageBox.Show(
|
||||
VisualStudio.UI.Resources.DeleteCommentConfirmation,
|
||||
VisualStudio.UI.Resources.DeleteCommentConfirmationCaption,
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question) == DialogResult.Yes;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
namespace GitHub.InlineReviews.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// This service allows for functionality to be injected into the chain of different peek Comment ViewModel types.
|
||||
/// </summary>
|
||||
public interface ICommentService
|
||||
{
|
||||
/// <summary>
|
||||
/// This function uses MessageBox.Show to display a confirmation if a comment should be deleted.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool ConfirmCommentDelete();
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ namespace GitHub.InlineReviews.Services
|
|||
/// </summary>
|
||||
/// <param name="repo">The repository</param>
|
||||
/// <param name="baseSha">The base commit SHA.</param>
|
||||
/// <param name="headSha">The head commit SHA.</param
|
||||
/// <param name="headSha">The head commit SHA.</param>
|
||||
/// <param name="relativePath">The path to the file in the repository.</param>
|
||||
/// <param name="contents">The byte array to compare with the base SHA.</param>
|
||||
/// <returns>
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace GitHub.InlineReviews.Services
|
|||
/// Gets the line number for a peek session tracking point.
|
||||
/// </summary>
|
||||
/// <param name="session">The peek session.</param>
|
||||
/// <param name="point">The peek session tracking point</param>
|
||||
/// <returns>
|
||||
/// A tuple containing the line number and whether the line number represents a line in the
|
||||
/// left hand side of a diff view.
|
||||
|
|
|
@ -230,13 +230,8 @@ namespace GitHub.InlineReviews.Services
|
|||
throw new InvalidOperationException("There is no pending review to cancel.");
|
||||
}
|
||||
|
||||
await service.CancelPendingReview(LocalRepository, PendingReviewId);
|
||||
|
||||
PullRequest.Reviews = PullRequest.Reviews
|
||||
.Where(x => x.Id != PendingReviewId)
|
||||
.ToList();
|
||||
|
||||
await Update(PullRequest);
|
||||
var pullRequest = await service.CancelPendingReview(LocalRepository, PendingReviewId);
|
||||
await Update(pullRequest);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace GitHub.InlineReviews.Services
|
|||
/// </summary>
|
||||
/// <param name="service">The PR service to use.</param>
|
||||
/// <param name="sessionService">The PR session service to use.</param>
|
||||
/// <param name="teamExplorerService">The team explorer service to use.</param>
|
||||
/// <param name="teamExplorerContext">The team explorer context to use.</param>
|
||||
[ImportingConstructor]
|
||||
public PullRequestSessionManager(
|
||||
IPullRequestService service,
|
||||
|
@ -225,7 +225,7 @@ namespace GitHub.InlineReviews.Services
|
|||
{
|
||||
PullRequestSession session = null;
|
||||
WeakReference<PullRequestSession> weakSession;
|
||||
var key = Tuple.Create(owner, number);
|
||||
var key = Tuple.Create(owner.ToLowerInvariant(), number);
|
||||
|
||||
if (sessions.TryGetValue(key, out weakSession))
|
||||
{
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.Reactive.Subjects;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Api;
|
||||
using GitHub.App.Services;
|
||||
using GitHub.Factories;
|
||||
using GitHub.InlineReviews.Models;
|
||||
using GitHub.Models;
|
||||
|
@ -17,6 +18,7 @@ using GitHub.Services;
|
|||
using LibGit2Sharp;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Projection;
|
||||
using Octokit;
|
||||
using Octokit.GraphQL;
|
||||
using Octokit.GraphQL.Core;
|
||||
using Octokit.GraphQL.Model;
|
||||
|
@ -24,6 +26,14 @@ using ReactiveUI;
|
|||
using Serilog;
|
||||
using PullRequestReviewEvent = Octokit.PullRequestReviewEvent;
|
||||
using static Octokit.GraphQL.Variable;
|
||||
using CheckAnnotationLevel = GitHub.Models.CheckAnnotationLevel;
|
||||
using CheckConclusionState = GitHub.Models.CheckConclusionState;
|
||||
using CheckStatusState = GitHub.Models.CheckStatusState;
|
||||
using DraftPullRequestReviewComment = Octokit.GraphQL.Model.DraftPullRequestReviewComment;
|
||||
using FileMode = System.IO.FileMode;
|
||||
using NotFoundException = LibGit2Sharp.NotFoundException;
|
||||
using PullRequestReviewState = Octokit.GraphQL.Model.PullRequestReviewState;
|
||||
using StatusState = GitHub.Models.StatusState;
|
||||
|
||||
// GraphQL DatabaseId field are marked as deprecated, but we need them for interop with REST.
|
||||
#pragma warning disable CS0618
|
||||
|
@ -38,6 +48,8 @@ namespace GitHub.InlineReviews.Services
|
|||
{
|
||||
static readonly ILogger log = LogManager.ForContext<PullRequestSessionService>();
|
||||
static ICompiledQuery<PullRequestDetailModel> readPullRequest;
|
||||
static ICompiledQuery<IEnumerable<LastCommitAdapter>> readCommitStatuses;
|
||||
static ICompiledQuery<IEnumerable<LastCommitAdapter>> readCommitStatusesEnterprise;
|
||||
static ICompiledQuery<ActorModel> readViewer;
|
||||
|
||||
readonly IGitService gitService;
|
||||
|
@ -290,14 +302,14 @@ namespace GitHub.InlineReviews.Services
|
|||
HeadRefName = pr.HeadRefName,
|
||||
HeadRefSha = pr.HeadRefOid,
|
||||
HeadRepositoryOwner = pr.HeadRepositoryOwner != null ? pr.HeadRepositoryOwner.Login : null,
|
||||
State = (PullRequestStateEnum)pr.State,
|
||||
State = pr.State.FromGraphQl(),
|
||||
UpdatedAt = pr.UpdatedAt,
|
||||
Reviews = pr.Reviews(null, null, null, null, null, null).AllPages().Select(review => new PullRequestReviewModel
|
||||
{
|
||||
Id = review.Id.Value,
|
||||
Body = review.Body,
|
||||
CommitId = review.Commit.Oid,
|
||||
State = (GitHub.Models.PullRequestReviewState)review.State,
|
||||
State = review.State.FromGraphQl(),
|
||||
SubmittedAt = review.SubmittedAt,
|
||||
Author = new ActorModel
|
||||
{
|
||||
|
@ -341,6 +353,10 @@ namespace GitHub.InlineReviews.Services
|
|||
|
||||
var apiClient = await apiClientFactory.Create(address);
|
||||
var files = await apiClient.GetPullRequestFiles(owner, name, number).ToList();
|
||||
var lastCommitModel = await GetPullRequestLastCommitAdapter(address, owner, name, number);
|
||||
|
||||
result.Statuses = lastCommitModel.Statuses;
|
||||
result.CheckSuites = lastCommitModel.CheckSuites;
|
||||
|
||||
result.ChangedFiles = files.Select(file => new PullRequestFileModel
|
||||
{
|
||||
|
@ -736,6 +752,85 @@ namespace GitHub.InlineReviews.Services
|
|||
return Task.Factory.StartNew(() => gitService.GetRepository(repository.LocalPath));
|
||||
}
|
||||
|
||||
async Task<LastCommitAdapter> GetPullRequestLastCommitAdapter(HostAddress address, string owner, string name, int number)
|
||||
{
|
||||
ICompiledQuery<IEnumerable<LastCommitAdapter>> query;
|
||||
if (address.IsGitHubDotCom())
|
||||
{
|
||||
if (readCommitStatuses == null)
|
||||
{
|
||||
readCommitStatuses = new Query()
|
||||
.Repository(Var(nameof(owner)), Var(nameof(name)))
|
||||
.PullRequest(Var(nameof(number))).Commits(last: 1).Nodes.Select(
|
||||
commit => new LastCommitAdapter
|
||||
{
|
||||
CheckSuites = commit.Commit.CheckSuites(null, null, null, null, null).AllPages(10)
|
||||
.Select(suite => new CheckSuiteModel
|
||||
{
|
||||
CheckRuns = suite.CheckRuns(null, null, null, null, null).AllPages(10)
|
||||
.Select(run => new CheckRunModel
|
||||
{
|
||||
Conclusion = run.Conclusion.FromGraphQl(),
|
||||
Status = run.Status.FromGraphQl(),
|
||||
Name = run.Name,
|
||||
DetailsUrl = run.Permalink,
|
||||
Summary = run.Summary,
|
||||
}).ToList()
|
||||
}).ToList(),
|
||||
Statuses = commit.Commit.Status
|
||||
.Select(context =>
|
||||
context.Contexts.Select(statusContext => new StatusModel
|
||||
{
|
||||
State = statusContext.State.FromGraphQl(),
|
||||
Context = statusContext.Context,
|
||||
TargetUrl = statusContext.TargetUrl,
|
||||
Description = statusContext.Description,
|
||||
}).ToList()
|
||||
).SingleOrDefault()
|
||||
}
|
||||
).Compile();
|
||||
}
|
||||
|
||||
query = readCommitStatuses;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (readCommitStatusesEnterprise == null)
|
||||
{
|
||||
readCommitStatusesEnterprise = new Query()
|
||||
.Repository(Var(nameof(owner)), Var(nameof(name)))
|
||||
.PullRequest(Var(nameof(number))).Commits(last: 1).Nodes.Select(
|
||||
commit => new LastCommitAdapter
|
||||
{
|
||||
Statuses = commit.Commit.Status
|
||||
.Select(context =>
|
||||
context.Contexts.Select(statusContext => new StatusModel
|
||||
{
|
||||
State = statusContext.State.FromGraphQl(),
|
||||
Context = statusContext.Context,
|
||||
TargetUrl = statusContext.TargetUrl,
|
||||
Description = statusContext.Description,
|
||||
}).ToList()
|
||||
).SingleOrDefault()
|
||||
}
|
||||
).Compile();
|
||||
}
|
||||
|
||||
query = readCommitStatusesEnterprise;
|
||||
}
|
||||
|
||||
var vars = new Dictionary<string, object>
|
||||
{
|
||||
{ nameof(owner), owner },
|
||||
{ nameof(name), name },
|
||||
{ nameof(number), number },
|
||||
};
|
||||
|
||||
var connection = await graphqlFactory.CreateConnection(address);
|
||||
var result = await connection.Run(query, vars);
|
||||
return result.First();
|
||||
}
|
||||
|
||||
static void BuildPullRequestThreads(PullRequestDetailModel model)
|
||||
{
|
||||
var commentsByReplyId = new Dictionary<string, List<CommentAdapter>>();
|
||||
|
@ -795,11 +890,6 @@ namespace GitHub.InlineReviews.Services
|
|||
model.Threads = threads;
|
||||
}
|
||||
|
||||
static GitHub.Models.PullRequestReviewState FromGraphQL(Octokit.GraphQL.Model.PullRequestReviewState s)
|
||||
{
|
||||
return (GitHub.Models.PullRequestReviewState)s;
|
||||
}
|
||||
|
||||
static Octokit.GraphQL.Model.PullRequestReviewEvent ToGraphQl(Octokit.PullRequestReviewEvent e)
|
||||
{
|
||||
switch (e)
|
||||
|
@ -825,5 +915,12 @@ namespace GitHub.InlineReviews.Services
|
|||
public string OriginalCommitId { get; set; }
|
||||
public string ReplyTo { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
class LastCommitAdapter
|
||||
{
|
||||
public List<CheckSuiteModel> CheckSuites { get; set; }
|
||||
|
||||
public List<StatusModel> Statuses { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@ using System.Linq;
|
|||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using GitHub.Extensions;
|
||||
using GitHub.InlineReviews.Services;
|
||||
using GitHub.Logging;
|
||||
using GitHub.Models;
|
||||
using GitHub.ViewModels;
|
||||
|
@ -18,6 +20,7 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
public class CommentViewModel : ReactiveObject, ICommentViewModel
|
||||
{
|
||||
static readonly ILogger log = LogManager.ForContext<CommentViewModel>();
|
||||
ICommentService commentService;
|
||||
string body;
|
||||
string errorMessage;
|
||||
bool isReadOnly;
|
||||
|
@ -30,6 +33,7 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CommentViewModel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="commentService">The comment service</param>
|
||||
/// <param name="thread">The thread that the comment is a part of.</param>
|
||||
/// <param name="currentUser">The current user.</param>
|
||||
/// <param name="pullRequestId">The pull request id of the comment.</param>
|
||||
|
@ -41,6 +45,7 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
/// <param name="updatedAt">The modified date of the comment.</param>
|
||||
/// <param name="webUrl"></param>
|
||||
protected CommentViewModel(
|
||||
ICommentService commentService,
|
||||
ICommentThreadViewModel thread,
|
||||
IActorViewModel currentUser,
|
||||
int pullRequestId,
|
||||
|
@ -52,6 +57,7 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
DateTimeOffset updatedAt,
|
||||
Uri webUrl)
|
||||
{
|
||||
this.commentService = commentService;
|
||||
Guard.ArgumentNotNull(thread, nameof(thread));
|
||||
Guard.ArgumentNotNull(currentUser, nameof(currentUser));
|
||||
Guard.ArgumentNotNull(author, nameof(author));
|
||||
|
@ -102,14 +108,17 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CommentViewModel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="commentService">Comment Service</param>
|
||||
/// <param name="thread">The thread that the comment is a part of.</param>
|
||||
/// <param name="currentUser">The current user.</param>
|
||||
/// <param name="model">The comment model.</param>
|
||||
protected CommentViewModel(
|
||||
ICommentService commentService,
|
||||
ICommentThreadViewModel thread,
|
||||
ActorModel currentUser,
|
||||
CommentModel model)
|
||||
: this(
|
||||
commentService,
|
||||
thread,
|
||||
new ActorViewModel(currentUser),
|
||||
model.PullRequestId,
|
||||
|
@ -130,22 +139,25 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
|
||||
async Task DoDelete(object unused)
|
||||
{
|
||||
try
|
||||
if (commentService.ConfirmCommentDelete())
|
||||
{
|
||||
ErrorMessage = null;
|
||||
IsSubmitting = true;
|
||||
try
|
||||
{
|
||||
ErrorMessage = null;
|
||||
IsSubmitting = true;
|
||||
|
||||
await Thread.DeleteComment.ExecuteAsyncTask(new Tuple<int, int>(PullRequestId, DatabaseId));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = e.Message;
|
||||
ErrorMessage = message;
|
||||
log.Error(e, "Error Deleting comment");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsSubmitting = false;
|
||||
await Thread.DeleteComment.ExecuteAsyncTask(new Tuple<int, int>(PullRequestId, DatabaseId));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = e.Message;
|
||||
ErrorMessage = message;
|
||||
log.Error(e, "Error Deleting comment");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsSubmitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ using GitHub.Extensions;
|
|||
using GitHub.Extensions.Reactive;
|
||||
using GitHub.Factories;
|
||||
using GitHub.InlineReviews.Commands;
|
||||
using GitHub.InlineReviews.Peek;
|
||||
using GitHub.InlineReviews.Services;
|
||||
using GitHub.Logging;
|
||||
using GitHub.Models;
|
||||
|
@ -31,6 +32,7 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
readonly IInlineCommentPeekService peekService;
|
||||
readonly IPeekSession peekSession;
|
||||
readonly IPullRequestSessionManager sessionManager;
|
||||
readonly ICommentService commentService;
|
||||
IPullRequestSession session;
|
||||
IPullRequestSessionFile file;
|
||||
ICommentThreadViewModel thread;
|
||||
|
@ -44,12 +46,12 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InlineCommentPeekViewModel"/> class.
|
||||
/// </summary>
|
||||
public InlineCommentPeekViewModel(
|
||||
IInlineCommentPeekService peekService,
|
||||
public InlineCommentPeekViewModel(IInlineCommentPeekService peekService,
|
||||
IPeekSession peekSession,
|
||||
IPullRequestSessionManager sessionManager,
|
||||
INextInlineCommentCommand nextCommentCommand,
|
||||
IPreviousInlineCommentCommand previousCommentCommand)
|
||||
IPreviousInlineCommentCommand previousCommentCommand,
|
||||
ICommentService commentService)
|
||||
{
|
||||
Guard.ArgumentNotNull(peekService, nameof(peekService));
|
||||
Guard.ArgumentNotNull(peekSession, nameof(peekSession));
|
||||
|
@ -60,6 +62,7 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
this.peekService = peekService;
|
||||
this.peekSession = peekSession;
|
||||
this.sessionManager = sessionManager;
|
||||
this.commentService = commentService;
|
||||
triggerPoint = peekSession.GetTriggerPoint(peekSession.TextView.TextBuffer);
|
||||
|
||||
peekSession.Dismissed += (s, e) => Dispose();
|
||||
|
@ -180,11 +183,11 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
|
||||
if (thread != null)
|
||||
{
|
||||
Thread = new InlineCommentThreadViewModel(session, thread.Comments);
|
||||
Thread = new InlineCommentThreadViewModel(commentService, session, thread.Comments);
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread = new NewInlineCommentThreadViewModel(session, file, lineNumber, leftBuffer);
|
||||
Thread = new NewInlineCommentThreadViewModel(commentService, session, file, lineNumber, leftBuffer);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(placeholderBody))
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Globalization;
|
|||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Extensions;
|
||||
using GitHub.InlineReviews.Services;
|
||||
using GitHub.Models;
|
||||
using GitHub.Services;
|
||||
using ReactiveUI;
|
||||
|
@ -18,10 +19,10 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InlineCommentThreadViewModel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="commentService">The comment service</param>
|
||||
/// <param name="session">The current PR review session.</param>
|
||||
/// <param name="comments">The comments to display in this inline review.</param>
|
||||
public InlineCommentThreadViewModel(
|
||||
IPullRequestSession session,
|
||||
public InlineCommentThreadViewModel(ICommentService commentService, IPullRequestSession session,
|
||||
IEnumerable<InlineCommentModel> comments)
|
||||
: base(session.User)
|
||||
{
|
||||
|
@ -45,13 +46,14 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
{
|
||||
Comments.Add(new PullRequestReviewCommentViewModel(
|
||||
session,
|
||||
commentService,
|
||||
this,
|
||||
CurrentUser,
|
||||
comment.Review,
|
||||
comment.Comment));
|
||||
}
|
||||
|
||||
Comments.Add(PullRequestReviewCommentViewModel.CreatePlaceholder(session, this, CurrentUser));
|
||||
Comments.Add(PullRequestReviewCommentViewModel.CreatePlaceholder(session, commentService, this, CurrentUser));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Reactive;
|
|||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Extensions;
|
||||
using GitHub.InlineReviews.Services;
|
||||
using GitHub.Models;
|
||||
using GitHub.Services;
|
||||
using ReactiveUI;
|
||||
|
@ -20,13 +21,14 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InlineCommentThreadViewModel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="commentService">The comment service</param>
|
||||
/// <param name="session">The current PR review session.</param>
|
||||
/// <param name="file">The file being commented on.</param>
|
||||
/// <param name="lineNumber">The 0-based line number in the file.</param>
|
||||
/// <param name="leftComparisonBuffer">
|
||||
/// True if the comment is being left on the left-hand-side of a diff; otherwise false.
|
||||
/// True if the comment is being left on the left-hand-side of a diff; otherwise false.
|
||||
/// </param>
|
||||
public NewInlineCommentThreadViewModel(
|
||||
public NewInlineCommentThreadViewModel(ICommentService commentService,
|
||||
IPullRequestSession session,
|
||||
IPullRequestSessionFile file,
|
||||
int lineNumber,
|
||||
|
@ -53,7 +55,7 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
Observable.Return(false),
|
||||
o => null);
|
||||
|
||||
var placeholder = PullRequestReviewCommentViewModel.CreatePlaceholder(session, this, CurrentUser);
|
||||
var placeholder = PullRequestReviewCommentViewModel.CreatePlaceholder(session, commentService, this, CurrentUser);
|
||||
placeholder.BeginEdit.Execute(null);
|
||||
this.WhenAnyValue(x => x.NeedsPush).Subscribe(x => placeholder.IsReadOnly = x);
|
||||
Comments.Add(placeholder);
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Reactive;
|
|||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Extensions;
|
||||
using GitHub.InlineReviews.Services;
|
||||
using GitHub.Logging;
|
||||
using GitHub.Models;
|
||||
using GitHub.Services;
|
||||
|
@ -28,6 +29,7 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
/// Initializes a new instance of the <see cref="PullRequestReviewCommentViewModel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="session">The pull request session.</param>
|
||||
/// <param name="commentService">The comment service</param>
|
||||
/// <param name="thread">The thread that the comment is a part of.</param>
|
||||
/// <param name="currentUser">The current user.</param>
|
||||
/// <param name="pullRequestId">The pull request id of the comment.</param>
|
||||
|
@ -39,7 +41,9 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
/// <param name="updatedAt">The modified date of the comment.</param>
|
||||
/// <param name="isPending">Whether this is a pending comment.</param>
|
||||
/// <param name="webUrl"></param>
|
||||
public PullRequestReviewCommentViewModel(IPullRequestSession session,
|
||||
public PullRequestReviewCommentViewModel(
|
||||
IPullRequestSession session,
|
||||
ICommentService commentService,
|
||||
ICommentThreadViewModel thread,
|
||||
IActorViewModel currentUser,
|
||||
int pullRequestId,
|
||||
|
@ -51,7 +55,7 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
DateTimeOffset updatedAt,
|
||||
bool isPending,
|
||||
Uri webUrl)
|
||||
: base(thread, currentUser, pullRequestId, commentId, databaseId, body, state, author, updatedAt, webUrl)
|
||||
: base(commentService, thread, currentUser, pullRequestId, commentId, databaseId, body, state, author, updatedAt, webUrl)
|
||||
{
|
||||
Guard.ArgumentNotNull(session, nameof(session));
|
||||
|
||||
|
@ -81,17 +85,21 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
/// Initializes a new instance of the <see cref="PullRequestReviewCommentViewModel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="session">The pull request session.</param>
|
||||
/// <param name="commentService">Comment Service</param>
|
||||
/// <param name="thread">The thread that the comment is a part of.</param>
|
||||
/// <param name="currentUser">The current user.</param>
|
||||
/// <param name="review">The associated pull request review.</param>
|
||||
/// <param name="model">The comment model.</param>
|
||||
public PullRequestReviewCommentViewModel(
|
||||
IPullRequestSession session,
|
||||
ICommentService commentService,
|
||||
ICommentThreadViewModel thread,
|
||||
IActorViewModel currentUser,
|
||||
PullRequestReviewModel review,
|
||||
PullRequestReviewCommentModel model)
|
||||
: this(
|
||||
session,
|
||||
commentService,
|
||||
thread,
|
||||
currentUser,
|
||||
model.PullRequestId,
|
||||
|
@ -110,16 +118,19 @@ namespace GitHub.InlineReviews.ViewModels
|
|||
/// Creates a placeholder comment which can be used to add a new comment to a thread.
|
||||
/// </summary>
|
||||
/// <param name="session">The pull request session.</param>
|
||||
/// <param name="commentService">Comment Service</param>
|
||||
/// <param name="thread">The comment thread.</param>
|
||||
/// <param name="currentUser">The current user.</param>
|
||||
/// <returns>THe placeholder comment.</returns>
|
||||
public static CommentViewModel CreatePlaceholder(
|
||||
IPullRequestSession session,
|
||||
ICommentService commentService,
|
||||
ICommentThreadViewModel thread,
|
||||
IActorViewModel currentUser)
|
||||
{
|
||||
return new PullRequestReviewCommentViewModel(
|
||||
session,
|
||||
commentService,
|
||||
thread,
|
||||
currentUser,
|
||||
0,
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
Height="16"
|
||||
Account="{Binding Author}"/>
|
||||
|
||||
<TextBlock Foreground="{DynamicResource GitHubVsToolWindowText}" FontWeight="Bold" Text="{Binding User.Login}" Margin="4 0"/>
|
||||
<TextBlock Foreground="{DynamicResource GitHubVsToolWindowText}" FontWeight="Bold" Text="{Binding Author.Login}" Margin="4 0"/>
|
||||
<ui:GitHubActionLink Content="{Binding UpdatedAt, Converter={ui:DurationToStringConverter}}"
|
||||
Command="{Binding OpenOnGitHub}"
|
||||
Foreground="{DynamicResource GitHubVsToolWindowText}"
|
||||
|
@ -74,9 +74,9 @@
|
|||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" DockPanel.Dock="Right"
|
||||
Visibility="{Binding CanDelete, Converter={ui:BooleanToVisibilityConverter}}">
|
||||
<ui:OcticonButton Command="{Binding BeginEdit}"
|
||||
Width="16"
|
||||
Height="16"
|
||||
Margin="0"
|
||||
Width="20"
|
||||
Margin="0 0 4 0"
|
||||
Background="Transparent"
|
||||
Foreground="{DynamicResource GitHubVsToolWindowText}"
|
||||
Icon="pencil"/>
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
<package id="Microsoft.VisualStudio.Validation" version="14.1.111" targetFramework="net452" />
|
||||
<package id="Microsoft.VSSDK.BuildTools" version="14.3.25407" targetFramework="net452" developmentDependency="true" />
|
||||
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" />
|
||||
<package id="Octokit.GraphQL" version="0.1.0-beta" targetFramework="net461" />
|
||||
<package id="Octokit.GraphQL" version="0.1.1-beta" targetFramework="net461" />
|
||||
<package id="Rx-Core" version="2.2.5-custom" targetFramework="net461" />
|
||||
<package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net461" />
|
||||
<package id="Rx-Linq" version="2.2.5-custom" targetFramework="net461" />
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace GitHub.Services.Vssdk.Commands
|
|||
/// Adds <see cref="IVsCommand"/>s or <see cref="IVsCommand{TParam}"/>s to a menu.
|
||||
/// </summary>
|
||||
/// <param name="service">The menu command service.</param>
|
||||
/// <param name="command">The commands to add.</param>
|
||||
/// <param name="commands">The commands to add.</param>
|
||||
public static void AddCommands(
|
||||
this IMenuCommandService service,
|
||||
params IVsCommandBase[] commands)
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче