Parse diffs to get real line numbers for comments.

The `PullRequestReviewComment.Position` field refers to a line number in
the diff between the PR `Head` and `Base`. Generate and parse this diff
to get the line number in the file at which the comment should be
placed.
This commit is contained in:
Steven Kirk 2017-04-07 15:12:52 +02:00
Родитель 8393f9d1e7
Коммит bb6aa7ef28
23 изменённых файлов: 634 добавлений и 105 удалений

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

@ -1,5 +1,6 @@
<ProjectConfiguration>
<Settings>
<PreventSigningOfAssembly>True</PreventSigningOfAssembly>
<PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully>
</Settings>
</ProjectConfiguration>

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

@ -6,8 +6,11 @@ namespace GitHub.Models
[NullGuard(ValidationFlags.None)]
public class PullRequestReviewCommentModel : IPullRequestReviewCommentModel
{
public int Id { get; set; }
public string Path { get; set; }
public int? Position { get; set; }
public string CommitId { get; set; }
public string DiffHunk { get; set; }
public IAccount User { get; set; }
public string Body { get; set; }
public DateTimeOffset UpdatedAt { get; set; }

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

@ -159,6 +159,35 @@ namespace GitHub.Services
});
}
public Task<Patch> Compare(
IRepository repository,
string sha1,
string sha2,
string path)
{
Guard.ArgumentNotNull(repository, nameof(repository));
Guard.ArgumentNotEmptyString(sha1, nameof(sha1));
Guard.ArgumentNotEmptyString(sha2, nameof(sha2));
return Task.Factory.StartNew(() =>
{
var commit1 = repository.Lookup<Commit>(sha1);
var commit2 = repository.Lookup<Commit>(sha2);
if (commit1 != null && commit2 != null)
{
return repository.Diff.Compare<Patch>(
commit1.Tree,
commit2.Tree,
new[] { path });
}
else
{
return null;
}
});
}
public Task<T> GetConfig<T>(IRepository repository, string key)
{
Guard.ArgumentNotNull(repository, nameof(repository));

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

@ -407,8 +407,11 @@ namespace GitHub.Services
Comments = prCacheItem.Comments.Select(x =>
(IPullRequestReviewCommentModel)new PullRequestReviewCommentModel
{
Id = x.Id,
Path = x.Path,
Position = x.Position,
CommitId = x.CommitId,
DiffHunk = x.DiffHunk,
User = Create(x.User),
Body = x.Body,
UpdatedAt = x.UpdatedAt,
@ -623,15 +626,21 @@ namespace GitHub.Services
public PullRequestReviewCommentCacheItem(PullRequestReviewComment comment)
{
Id = comment.Id;
Path = comment.Path;
Position = comment.Position;
CommitId = comment.CommitId;
DiffHunk = comment.DiffHunk;
User = new AccountCacheItem(comment.User);
Body = comment.Body;
UpdatedAt = comment.UpdatedAt;
}
public int Id { get; }
public string Path { get; set; }
public int? Position { get; set; }
public string CommitId { get; set; }
public string DiffHunk { get; set; }
public AccountCacheItem User { get; set; }
public string Body { get; set; }
public DateTimeOffset UpdatedAt { get; set; }

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

@ -12,37 +12,23 @@ namespace GitHub.Services
Dictionary<string, List<IPullRequestReviewCommentModel>> comments;
public PullRequestReviewSession(
ILocalRepositoryModel repository,
IEnumerable<IPullRequestReviewCommentModel> comments)
IPullRequestModel pullRequest,
ILocalRepositoryModel repository)
{
PullRequest = pullRequest;
Repository = repository;
this.comments = comments.GroupBy(x => x.Path)
this.comments = pullRequest.Comments.GroupBy(x => x.Path)
.ToDictionary(x => x.Key, x => x.ToList());
}
public IPullRequestModel PullRequest { get; }
public ILocalRepositoryModel Repository { get; }
public IEnumerable<IPullRequestReviewCommentModel> GetCommentsForFile(string filePath)
public IReadOnlyList<IPullRequestReviewCommentModel> GetCommentsForFile(string path)
{
var relativePath = RootedPathToRelativePath(filePath).Replace('\\', '/');
var result = Empty;
comments.TryGetValue(relativePath, out result);
return result ?? Empty;
}
string RootedPathToRelativePath(string path)
{
if (Path.IsPathRooted(path))
{
var repoRoot = Repository.LocalPath;
if (path.StartsWith(repoRoot) && path.Length > repoRoot.Length + 1)
{
return path.Substring(repoRoot.Length + 1);
}
}
return null;
List<IPullRequestReviewCommentModel> result;
path = path.Replace('\\', '/');
return comments.TryGetValue(path, out result) ? result : Empty;
}
}
}

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

@ -486,7 +486,7 @@ namespace GitHub.ViewModels
{
if (IsCheckedOut)
{
var session = new PullRequestReviewSession(repository, Model.Comments);
var session = new PullRequestReviewSession(Model, repository);
sessionManager.NotifySessionChanged(session);
}
else

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

@ -68,6 +68,18 @@ namespace GitHub.Services
/// </returns>
Task<TreeChanges> Compare(IRepository repository, string sha1, string sha2, bool detectRenames = false);
/// <summary>
/// Compares a file in two commits.
/// </summary>
/// <param name="repository">The repository</param>
/// <param name="sha1">The SHA of the first commit.</param>
/// <param name="sha2">The SHA of the second commit.</param>
/// <param name="path">The relative path to the file.</param>
/// <returns>
/// A <see cref="Patch"/> object or null if one of the commits or the file could not be found in the repository.
/// </returns>
Task<Patch> Compare(IRepository repository, string sha1, string sha2, string path);
/// Gets the value of a configuration key.
/// </summary>
/// <param name="repository">The repository.</param>

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

@ -4,8 +4,11 @@ namespace GitHub.Models
{
public interface IPullRequestReviewCommentModel
{
int Id { get; }
string Path { get; }
int? Position { get; }
string CommitId { get; }
string DiffHunk { get; }
IAccount User { get; }
string Body { get; }
DateTimeOffset UpdatedAt { get; }

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

@ -6,6 +6,9 @@ namespace GitHub.Services
{
public interface IPullRequestReviewSession
{
IEnumerable<IPullRequestReviewCommentModel> GetCommentsForFile(string filePath);
IPullRequestModel PullRequest { get; }
ILocalRepositoryModel Repository { get; }
IReadOnlyList<IPullRequestReviewCommentModel> GetCommentsForFile(string path);
}
}

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

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\packages\LibGit2Sharp.NativeBinaries.1.0.129\build\LibGit2Sharp.NativeBinaries.props" Condition="Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.129\build\LibGit2Sharp.NativeBinaries.props')" />
<Import Project="..\..\packages\Microsoft.VSSDK.BuildTools.14.3.25407\build\Microsoft.VSSDK.BuildTools.props" Condition="Exists('..\..\packages\Microsoft.VSSDK.BuildTools.14.3.25407\build\Microsoft.VSSDK.BuildTools.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
@ -53,7 +54,11 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\common\SolutionInfo.cs">
<Link>Properties\SolutionInfo.cs</Link>
</Compile>
<Compile Include="InlineReviewsPackage.cs" />
<Compile Include="Models\InlineCommentModel.cs" />
<Compile Include="Peek\ReviewPeekableItem.cs" />
<Compile Include="Peek\ReviewPeekableItemSource.cs" />
<Compile Include="Peek\ReviewPeekableItemSourceProvider.cs" />
@ -65,6 +70,8 @@
<Compile Include="Peek\ReviewPeekSessionCreationOptions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SampleData\PullRequestReviewCommentDesigner.cs" />
<Compile Include="Services\IInlineCommentBuilder.cs" />
<Compile Include="Services\InlineCommentBuilder.cs" />
<Compile Include="Tags\ReviewGlyph.xaml.cs">
<DependentUpon>ReviewGlyph.xaml</DependentUpon>
</Compile>
@ -104,6 +111,10 @@
<Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project>
<Name>GitHub.Exports</Name>
</ProjectReference>
<ProjectReference Include="..\GitHub.Extensions\GitHub.Extensions.csproj">
<Project>{6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}</Project>
<Name>GitHub.Extensions</Name>
</ProjectReference>
<ProjectReference Include="..\GitHub.UI\GitHub.UI.csproj">
<Project>{346384dd-2445-4a28-af22-b45f3957bd89}</Project>
<Name>GitHub.UI</Name>
@ -126,6 +137,10 @@
<Reference Include="EnvDTE90, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<EmbedInteropTypes>False</EmbedInteropTypes>
</Reference>
<Reference Include="LibGit2Sharp, Version=0.22.0.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL">
<HintPath>..\..\packages\LibGit2Sharp.0.22.0\lib\net40\LibGit2Sharp.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Build.Framework" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.VisualStudio.CommandBars, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
@ -279,14 +294,16 @@
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" />
<Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != '' And '$(NCrunch)' != '1'" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\Microsoft.VSSDK.BuildTools.14.3.25407\build\Microsoft.VSSDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.VSSDK.BuildTools.14.3.25407\build\Microsoft.VSSDK.BuildTools.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.VSSDK.BuildTools.14.3.25407\build\Microsoft.VSSDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.VSSDK.BuildTools.14.3.25407\build\Microsoft.VSSDK.BuildTools.targets'))" />
<Error Condition="!Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.129\build\LibGit2Sharp.NativeBinaries.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\LibGit2Sharp.NativeBinaries.1.0.129\build\LibGit2Sharp.NativeBinaries.props'))" />
</Target>
<Import Project="..\..\packages\Microsoft.VSSDK.BuildTools.14.3.25407\build\Microsoft.VSSDK.BuildTools.targets" Condition="Exists('..\..\packages\Microsoft.VSSDK.BuildTools.14.3.25407\build\Microsoft.VSSDK.BuildTools.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

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

@ -0,0 +1,25 @@
using System;
using GitHub.Models;
namespace GitHub.InlineReviews.Models
{
public class InlineCommentModel
{
public InlineCommentModel(int lineNumber, IPullRequestReviewCommentModel original)
{
LineNumber = lineNumber;
Original = original;
}
public int LineNumber { get; private set; }
public IPullRequestReviewCommentModel Original { get; }
public void UpdatePosition(int editLine, int editDelta)
{
if (LineNumber >= editLine)
{
LineNumber += editDelta;
}
}
}
}

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

@ -1,33 +1,6 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("GitHub.InlineReviews")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("GitHub.InlineReviews")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyDescription("Provides inline viewing of PR review comments")]
[assembly: Guid("3bf91177-3d16-425d-9c62-50a86cf26298")]

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

@ -6,8 +6,11 @@ namespace GitHub.InlineReviews.SampleData
{
public class PullRequestReviewCommentDesigner : IPullRequestReviewCommentModel
{
public int Id => 1;
public string Path => string.Empty;
public int? Position => 1;
public string CommitId => null;
public string DiffHunk => null;
public DateTimeOffset UpdatedAt => DateTime.Now.Subtract(TimeSpan.FromDays(3));
public IAccount User => new AccountDesigner { Login = "shana", IsUser = true };
public string Body => @"You can use a `CompositeDisposable` type here, it's designed to handle disposables in an optimal way (you can just call `Dispose()` on it and it will handle disposing everything it holds).";

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

@ -0,0 +1,14 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using GitHub.InlineReviews.Models;
using GitHub.Services;
namespace GitHub.InlineReviews.Services
{
public interface IInlineCommentBuilder
{
Task<IList<InlineCommentModel>> Build(
string path,
IPullRequestReviewSession session);
}
}

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

@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using GitHub.Extensions;
using GitHub.InlineReviews.Models;
using GitHub.Services;
using LibGit2Sharp;
namespace GitHub.InlineReviews.Services
{
[Export(typeof(IInlineCommentBuilder))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class InlineCommentBuilder : IInlineCommentBuilder
{
readonly IGitClient gitClient;
[ImportingConstructor]
public InlineCommentBuilder(IGitClient gitClient)
{
Guard.ArgumentNotNull(gitClient, nameof(gitClient));
this.gitClient = gitClient;
}
public async Task<IList<InlineCommentModel>> Build(
string path,
IPullRequestReviewSession session)
{
Guard.ArgumentNotNull(path, nameof(path));
Guard.ArgumentNotNull(session, nameof(session));
var result = new List<InlineCommentModel>();
var repo = GitService.GitServiceHelper.GetRepository(session.Repository.LocalPath);
var changes = await gitClient.Compare(
repo,
session.PullRequest.Head.Sha,
session.PullRequest.Base.Sha,
path);
var comments = session.GetCommentsForFile(path);
var diffPositions = comments
.Where(x => x.Position.HasValue)
.Select(x => x.Position.Value)
.OrderBy(x => x)
.Distinct();
var lineMap = MapDiffPositions(changes.Content, diffPositions);
foreach (var comment in comments)
{
int lineNumber;
if (comment.Position.HasValue && lineMap.TryGetValue(comment.Position.Value, out lineNumber))
{
result.Add(new InlineCommentModel(lineNumber, comment));
}
}
return result;
}
/// <summary>
/// Maps lines in a diff to lines in the source file.
/// </summary>
/// <param name="diff">The diff.</param>
/// <param name="positions">The diff lines to map.</param>
/// <returns>
/// A dictionary mapping 1-based diff line numbers to 0-based file line numbers.
/// </returns>
public IDictionary<int, int> MapDiffPositions(string diff, IEnumerable<int> positions)
{
Guard.ArgumentNotNull(diff, nameof(diff));
Guard.ArgumentNotNull(positions, nameof(positions));
var diffLine = -1;
var sourceLine = -1;
var positionEnumerator = positions.GetEnumerator();
var result = new Dictionary<int, int>();
positionEnumerator.MoveNext();
using (var reader = new StringReader(diff))
{
string line;
while ((line = reader.ReadLine()) != null)
{
if (line.StartsWith("@@"))
{
if (sourceLine == -1) diffLine = 0;
sourceLine = ReadLineFromHunkHeader(line) - 1;
}
if (positionEnumerator.Current == diffLine)
{
result.Add(diffLine, sourceLine);
if (!positionEnumerator.MoveNext()) break;
}
if (diffLine >= 0)
{
++diffLine;
if (!line.StartsWith('-')) ++sourceLine;
}
}
}
return result;
}
int ReadLineFromHunkHeader(string line)
{
int plus = line.IndexOf('+');
int comma = line.IndexOf(',', plus);
return int.Parse(line.Substring(plus + 1, comma - (plus + 1)));
}
public class ChunkRange
{
public int DiffLine { get; set; }
public int SourceLine { get; set; }
}
}
}

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

@ -5,6 +5,11 @@ using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
using GitHub.Services;
using GitHub.Models;
using System.IO;
using LibGit2Sharp;
using GitHub.InlineReviews.Models;
using GitHub.InlineReviews.Services;
using System.Threading.Tasks;
namespace GitHub.InlineReviews.Tags
{
@ -12,13 +17,19 @@ namespace GitHub.InlineReviews.Tags
{
readonly ITextBuffer buffer;
readonly IPullRequestReviewSessionManager sessionManager;
readonly IInlineCommentBuilder builder;
readonly IDisposable subscription;
List<InlineComment> comments;
string path;
IList<InlineCommentModel> comments;
public ReviewTagger(ITextBuffer buffer, IPullRequestReviewSessionManager sessionManager)
public ReviewTagger(
ITextBuffer buffer,
IPullRequestReviewSessionManager sessionManager,
IInlineCommentBuilder builder)
{
this.buffer = buffer;
this.sessionManager = sessionManager;
this.builder = builder;
this.buffer.Changed += Buffer_Changed;
subscription = sessionManager.SessionChanged.Subscribe(SessionChanged);
}
@ -40,9 +51,9 @@ namespace GitHub.InlineReviews.Tags
var endLine = span.End.GetContainingLine().LineNumber;
var spanComments = comments.Where(x =>
x.Position >= startLine &&
x.Position <= endLine)
.GroupBy(x => x.Position);
x.LineNumber >= startLine &&
x.LineNumber <= endLine)
.GroupBy(x => x.LineNumber);
foreach (var entry in spanComments)
{
@ -55,35 +66,37 @@ namespace GitHub.InlineReviews.Tags
}
}
void SessionChanged(IPullRequestReviewSession session)
static string RootedPathToRelativePath(string path, string basePath)
{
if (session != null)
if (Path.IsPathRooted(path))
{
var document = buffer.Properties.GetProperty<ITextDocument>(typeof(ITextDocument));
comments = CreateInlineComments(session.GetCommentsForFile(document.FilePath));
var entireFile = new SnapshotSpan(buffer.CurrentSnapshot, 0, buffer.CurrentSnapshot.Length);
TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(entireFile));
}
else
{
comments = null;
}
}
List<InlineComment> CreateInlineComments(IEnumerable<IPullRequestReviewCommentModel> comments)
{
var result = new List<InlineComment>();
foreach (var comment in comments)
{
if (comment.Position.HasValue)
if (path.StartsWith(basePath) && path.Length > basePath.Length + 1)
{
result.Add(new InlineComment(comment));
return path.Substring(basePath.Length + 1);
}
}
return result;
return null;
}
void NotifyTagsChanged()
{
var entireFile = new SnapshotSpan(buffer.CurrentSnapshot, 0, buffer.CurrentSnapshot.Length);
TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(entireFile));
}
async void SessionChanged(IPullRequestReviewSession session)
{
comments = null;
NotifyTagsChanged();
if (session != null)
{
var document = buffer.Properties.GetProperty<ITextDocument>(typeof(ITextDocument));
path = RootedPathToRelativePath(document.FilePath, session.Repository.LocalPath);
comments = await builder.Build(path, session);
NotifyTagsChanged();
}
}
void Buffer_Changed(object sender, TextContentChangedEventArgs e)
@ -101,25 +114,5 @@ namespace GitHub.InlineReviews.Tags
}
}
}
class InlineComment
{
public InlineComment(IPullRequestReviewCommentModel original)
{
Position = original.Position.Value - 1;
Original = original;
}
public int Position { get; private set; }
public IPullRequestReviewCommentModel Original { get; }
public void UpdatePosition(int editLine, int editDelta)
{
if (Position >= editLine)
{
Position += editDelta;
}
}
}
}
}

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

@ -1,5 +1,6 @@
using System;
using System.ComponentModel.Composition;
using GitHub.InlineReviews.Services;
using GitHub.Services;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
@ -13,16 +14,20 @@ namespace GitHub.InlineReviews.Tags
class ReviewTaggerProvider : ITaggerProvider
{
readonly IPullRequestReviewSessionManager sessionManager;
readonly IInlineCommentBuilder builder;
[ImportingConstructor]
public ReviewTaggerProvider(IPullRequestReviewSessionManager sessionManager)
public ReviewTaggerProvider(
IPullRequestReviewSessionManager sessionManager,
IInlineCommentBuilder builder)
{
this.sessionManager = sessionManager;
this.builder = builder;
}
public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
{
return new ReviewTagger(buffer, sessionManager) as ITagger<T>;
return new ReviewTagger(buffer, sessionManager, builder) as ITagger<T>;
}
}
}

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

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="LibGit2Sharp" version="0.22.0" targetFramework="net461" />
<package id="LibGit2Sharp.NativeBinaries" version="1.0.129" targetFramework="net461" />
<package id="Microsoft.VisualStudio.CoreUtility" version="14.3.25407" targetFramework="net461" />
<package id="Microsoft.VisualStudio.Imaging" version="14.3.25407" targetFramework="net452" />
<package id="Microsoft.VisualStudio.Language.Intellisense" version="14.3.25407" targetFramework="net461" />

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

@ -0,0 +1,22 @@
using System;
using GitHub.InlineReviews.Services;
using GitHub.Services;
using NSubstitute;
using UnitTests.Properties;
using Xunit;
namespace UnitTests.GitHub.InlineReviews
{
public class InlineCommentBuilderTests
{
[Fact]
public void CorrectlyMapsCommentPositions()
{
var target = new InlineCommentBuilder(Substitute.For<IGitClient>());
var diff = Resources.pr_573_apiclient_diff;
var result = target.MapDiffPositions(diff, new[] { 31 });
Assert.Equal(87, result[31]);
}
}
}

89
src/UnitTests/Properties/Resources.Designer.cs сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,89 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace UnitTests.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// 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.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UnitTests.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to diff --git a/src/GitHub.App/Api/ApiClient.cs b/src/GitHub.App/Api/ApiClient.cs
///index 6745236..79adcf3 100644
///--- a/src/GitHub.App/Api/ApiClient.cs
///+++ b/src/GitHub.App/Api/ApiClient.cs
///@@ -8,6 +8,7 @@
/// using System.Security.Cryptography;
/// using System.Text;
/// using GitHub.Primitives;
///+using NLog;
/// using NullGuard;
/// using Octokit;
/// using Octokit.Reactive;
///@@ -18,8 +19,6 @@
/// using System.Collections.Generic;
/// using GitHub.Models;
/// using GitHub.Extensions;
///-using GitHub.Infrastructure;
///-using S [rest of string was truncated]&quot;;.
/// </summary>
internal static string pr_573_apiclient_diff {
get {
return ResourceManager.GetString("pr_573_apiclient_diff", resourceCulture);
}
}
}
}

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

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="pr_573_apiclient_diff" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\pr-573-apiclient-diff.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value>
</data>
</root>

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

@ -0,0 +1,74 @@
diff --git a/src/GitHub.App/Api/ApiClient.cs b/src/GitHub.App/Api/ApiClient.cs
index 6745236..79adcf3 100644
--- a/src/GitHub.App/Api/ApiClient.cs
+++ b/src/GitHub.App/Api/ApiClient.cs
@@ -8,6 +8,7 @@
using System.Security.Cryptography;
using System.Text;
using GitHub.Primitives;
+using NLog;
using NullGuard;
using Octokit;
using Octokit.Reactive;
@@ -18,8 +19,6 @@
using System.Collections.Generic;
using GitHub.Models;
using GitHub.Extensions;
-using GitHub.Infrastructure;
-using Serilog;
namespace GitHub.Api
{
@@ -27,7 +26,7 @@ public partial class ApiClient : IApiClient
{
const string ScopesHeader = "X-OAuth-Scopes";
const string ProductName = Info.ApplicationInfo.ApplicationDescription;
- static readonly ILogger log = LogManager.ForContext<ApiClient>();
+ static readonly Logger log = LogManager.GetCurrentClassLogger();
static readonly Uri userEndpoint = new Uri("user", UriKind.Relative);
readonly IObservableGitHubClient gitHubClient;
@@ -85,7 +84,7 @@ public IObservable<UserAndScopes> GetUser()
}
else
{
- log.Error("Error reading scopes: /user succeeded but {ScopesHeader} was not present.", ScopesHeader);
+ log.Error($"Error reading scopes: /user succeeded but {ScopesHeader} was not present.");
}
return new UserAndScopes(response.Body, scopes);
@@ -176,7 +175,7 @@ static string GetSha256Hash(string input)
}
catch (Exception e)
{
- log.Error(e, "IMPOSSIBLE! Generating Sha256 hash caused an exception.");
+ log.Error("IMPOSSIBLE! Generating Sha256 hash caused an exception.", e);
return null;
}
}
@@ -194,14 +193,14 @@ static string GetMachineNameSafe()
}
catch (Exception e)
{
- log.Information(e, "Failed to retrieve host name using `DNS.GetHostName`.");
+ log.Info("Failed to retrieve host name using `DNS.GetHostName`.", e);
try
{
return Environment.MachineName;
}
catch (Exception ex)
{
- log.Information(ex, "Failed to retrieve host name using `Environment.MachineName`.");
+ log.Info("Failed to retrieve host name using `Environment.MachineName`.", ex);
return "(unknown)";
}
}
@@ -222,7 +221,7 @@ static string GetMachineIdentifier()
}
catch (Exception e)
{
- log.Information(e, "Could not retrieve MAC address. Fallback to using machine name.");
+ log.Info("Could not retrieve MAC address. Fallback to using machine name.", e);
return GetMachineNameSafe();
}
}

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

@ -246,6 +246,7 @@
<Compile Include="GitHub.Exports\LocalRepositoryModelTests.cs" />
<Compile Include="GitHub.Extensions\GuardTests.cs" />
<Compile Include="GitHub.Extensions\UriExtensionTests.cs" />
<Compile Include="GitHub.InlineReviews\InlineCommentBuilderTests.cs" />
<Compile Include="GitHub.Primitives\UriStringTests.cs" />
<Compile Include="GitHub.UI\Converters.cs" />
<Compile Include="GitHub.UI\TestAutomation\ResourceValueTests.cs" />
@ -260,6 +261,11 @@
<Compile Include="Helpers\TestBaseClass.cs" />
<Compile Include="Helpers\TestLoginCache.cs" />
<Compile Include="Helpers\TestSharedCache.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Substitutes.cs" />
<Compile Include="TestDoubles\FakeCommitLog.cs" />
<Compile Include="TestDoubles\FakeMenuCommandService.cs" />
@ -333,6 +339,10 @@
<Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project>
<Name>GitHub.Extensions</Name>
</ProjectReference>
<ProjectReference Include="..\GitHub.InlineReviews\GitHub.InlineReviews.csproj">
<Project>{7f5ed78b-74a3-4406-a299-70cfb5885b8b}</Project>
<Name>GitHub.InlineReviews</Name>
</ProjectReference>
<ProjectReference Include="..\GitHub.TeamFoundation.14\GitHub.TeamFoundation.14.csproj">
<Project>{161DBF01-1DBF-4B00-8551-C5C00F26720D}</Project>
<Name>GitHub.TeamFoundation.14</Name>
@ -363,6 +373,13 @@
</ItemGroup>
<ItemGroup>
<Content Include="FodyWeavers.xml" />
<None Include="Resources\pr-573-apiclient-diff.txt" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">