Merge branch 'master' into fixes/1859-missing-MEF-exports

This commit is contained in:
Jamie Cansdale 2018-08-24 12:13:20 +01:00 коммит произвёл GitHub
Родитель d5a9bc3182 208c34ea14
Коммит e4d831b5e0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
163 изменённых файлов: 2857 добавлений и 469 удалений

Просмотреть файл

@ -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.

Двоичные данные
lib/Octokit.GraphQL.0.1.1-beta.nupkg Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -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" />

24
src/GitHub.App/Resources.Designer.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&lt;Microsoft.VisualStudio.Shell.ServiceProgressData&gt;, 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&lt;Microsoft.VisualStudio.Shell.ServiceProgressData&gt;, 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)

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше