Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reactive;
using System.Text;
using System.Threading.Tasks;
using GitHub.Models;
using GitHub.Services;
Expand DownExpand Up@@ -93,14 +94,18 @@ public PullRequestDetailViewModelDesigner()
public ReactiveCommand<Unit> Pull { get; }
public ReactiveCommand<Unit> Push { get; }
public ReactiveCommand<object> OpenOnGitHub { get; }
public ReactiveCommand<object> OpenFile { get; }
public ReactiveCommand<object> DiffFile { get; }
public ReactiveCommand<object> DiffFileWithWorkingDirectory { get; }
public ReactiveCommand<object> OpenFileInWorkingDirectory { get; }
public ReactiveCommand<object> ViewFile { get; }

public Task<Tuple<string, string>> ExtractDiffFiles(IPullRequestFileNode file)
public Task<string> ExtractFile(IPullRequestFileNode file, bool head, Encoding encoding)
{
return null;
}

public Encoding GetEncoding(string path) => Encoding.UTF8;

public string GetLocalFilePath(IPullRequestFileNode file)
{
return null;
Expand Down
74 changes: 46 additions & 28 deletions src/GitHub.App/Services/PullRequestService.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,7 +14,6 @@
using System.Reactive;
using System.Collections.Generic;
using LibGit2Sharp;
using PullRequest = Octokit.PullRequest;
using System.Diagnostics;

namespace GitHub.Services
Expand DownExpand Up@@ -290,51 +289,51 @@ public IObservable<Tuple<string, int>> GetPullRequestForCurrentBranch(ILocalRepo
});
}

public IObservable<Tuple<string, string>> ExtractDiffFiles(
public IObservable<string> ExtractFile(
ILocalRepositoryModel repository,
IPullRequestModel pullRequest,
string fileName,
bool isPullRequestBranchCheckedOut)
bool head,
Encoding encoding)
{
return Observable.Defer(async () =>
{
var repo = gitService.GetRepository(repository.LocalPath);
var baseUrl = pullRequest.Base.RepositoryCloneUrl;
var headUrl = pullRequest.Head.RepositoryCloneUrl;
var baseSha = pullRequest.Base.Sha;
var headSha = pullRequest.Head.Sha;
var baseRef = pullRequest.Base.Ref;
var headRef = pullRequest.Head.Ref;
string mergeBase = await gitClient.GetPullRequestMergeBase(
repo, baseUrl, headUrl, baseSha, headSha, baseRef, headRef);
if (mergeBase == null)
{
throw new FileNotFoundException($"Couldn't find merge base between {baseSha} and {headSha}.");
}
var remote = await gitClient.GetHttpRemote(repo, "origin");
string sha;

string left;
string right;
if (isPullRequestBranchCheckedOut)
if (head)
{
right = Path.Combine(repository.LocalPath, fileName);
left = await ExtractToTempFile(repo, mergeBase, fileName, GetEncoding(right));
sha = pullRequest.Head.Sha;
}
else
{
left = await ExtractToTempFile(repo, mergeBase, fileName, Encoding.UTF8);
right = await ExtractToTempFile(repo, headSha, fileName, Encoding.UTF8);
sha = await gitClient.GetPullRequestMergeBase(
repo,
pullRequest.Base.RepositoryCloneUrl,
pullRequest.Head.RepositoryCloneUrl,
pullRequest.Base.Sha,
pullRequest.Head.Sha,
pullRequest.Base.Ref,
pullRequest.Head.Ref);

if (sha == null)
{
throw new NotFoundException($"Couldn't find merge base between {pullRequest.Base.Sha} and {pullRequest.Head.Sha}.");
}
}

return Observable.Return(Tuple.Create(left, right));
var file = await ExtractToTempFile(repo, pullRequest.Number, sha, fileName, encoding);
return Observable.Return(file);
});
}

static Encoding GetEncoding(string file)
public Encoding GetEncoding(string path)
{
if (File.Exists(file))
if (File.Exists(path))
{
var encoding = Encoding.UTF8;
if (HasPreamble(file, encoding))
if (HasPreamble(path, encoding))
{
return encoding;
}
Expand DownExpand Up@@ -413,9 +412,27 @@ string CreateUniqueRemoteName(IRepository repo, string name)
return uniqueName;
}

async Task<string> ExtractToTempFile(IRepository repo, string commitSha, string fileName, Encoding encoding)
async Task<string> ExtractToTempFile(
IRepository repo,
int pullRequestNumber,
string commitSha,
string fileName,
Encoding encoding)
{
var contents = await gitClient.ExtractFile(repo, commitSha, fileName) ?? string.Empty;
string contents;

try
{
contents = await gitClient.ExtractFile(repo, commitSha, fileName) ?? string.Empty;
}
catch (FileNotFoundException)
{
var pullHeadRef = $"refs/pull/{pullRequestNumber}/head";
var remote = await gitClient.GetHttpRemote(repo, "origin");
await gitClient.Fetch(repo, remote.Name, commitSha, pullHeadRef);
contents = await gitClient.ExtractFile(repo, commitSha, fileName) ?? string.Empty;
}

return CreateTempFile(fileName, commitSha, contents, encoding);
}

Expand All@@ -427,6 +444,7 @@ static string CreateTempFile(string fileName, string commitSha, string contents,

Directory.CreateDirectory(tempDir);
File.WriteAllText(tempFile, contents, encoding);
File.SetAttributes(tempFile, FileAttributes.ReadOnly);
return tempFile;
}

Expand Down
43 changes: 34 additions & 9 deletions src/GitHub.App/ViewModels/PullRequestDetailViewModel.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Text;
using System.Threading.Tasks;
using GitHub.App;
using GitHub.Exports;
Expand DownExpand Up@@ -112,8 +113,10 @@ public PullRequestDetailViewModel(
SubscribeOperationError(Push);

OpenOnGitHub = ReactiveCommand.Create();
OpenFile = ReactiveCommand.Create(this.WhenAnyValue(x => x.IsCheckedOut));
DiffFile = ReactiveCommand.Create();
DiffFileWithWorkingDirectory = ReactiveCommand.Create(this.WhenAnyValue(x => x.IsCheckedOut));
OpenFileInWorkingDirectory = ReactiveCommand.Create(this.WhenAnyValue(x => x.IsCheckedOut));
ViewFile = ReactiveCommand.Create();
}

/// <summary>
Expand DownExpand Up@@ -293,14 +296,25 @@ public IReadOnlyList<IPullRequestChangeNode> ChangedFilesTree
public ReactiveCommand<object> OpenOnGitHub { get; }

/// <summary>
/// Gets a command that opens a <see cref="IPullRequestFileNode"/>.
/// Gets a command that diffs an <see cref="IPullRequestFileNode"/> between BASE and HEAD.
/// </summary>
public ReactiveCommand<object> OpenFile { get; }
public ReactiveCommand<object> DiffFile { get; }

/// <summary>
/// Gets a command that diffs a <see cref="IPullRequestFileNode"/>.
/// Gets a command that diffs an <see cref="IPullRequestFileNode"/> between the version in
/// the working directory and HEAD.
/// </summary>
public ReactiveCommand<object> DiffFile { get; }
public ReactiveCommand<object> DiffFileWithWorkingDirectory { get; }

/// <summary>
/// Gets a command that opens an <see cref="IPullRequestFileNode"/> from disk.
/// </summary>
public ReactiveCommand<object> OpenFileInWorkingDirectory { get; }

/// <summary>
/// Gets a command that opens an <see cref="IPullRequestFileNode"/> as it appears in the PR.
/// </summary>
public ReactiveCommand<object> ViewFile { get; }

/// <summary>
/// Initializes the view model with new data.
Expand DownExpand Up@@ -455,16 +469,27 @@ public async Task Load(IRemoteRepositoryModel remoteRepository, IPullRequestMode
}

/// <summary>
/// Gets the before and after files needed for viewing a diff.
/// Gets a file as it appears in the pull request.
/// </summary>
/// <param name="file">The changed file.</param>
/// <returns>A tuple containing the full path to the before and after files.</returns>
public Task<Tuple<string, string>> ExtractDiffFiles(IPullRequestFileNode file)
/// <param name="head">
/// If true, gets the file at the PR head, otherwise gets the file at the PR merge base.
/// </param>
/// <param name="encoding">The encoding to use.</param>
/// <returns>The path to a temporary file.</returns>
public Task<string> ExtractFile(IPullRequestFileNode file, bool head, Encoding encoding)
{
var path = Path.Combine(file.DirectoryPath, file.FileName);
return pullRequestsService.ExtractDiffFiles(LocalRepository, model, path, IsCheckedOut).ToTask();
return pullRequestsService.ExtractFile(LocalRepository, model, path, head, encoding).ToTask();
}

/// <summary>
/// Gets the encoding for the specified file.
/// </summary>
/// <param name="path">The path to the file.</param>
/// <returns>The file's encoding</returns>
public Encoding GetEncoding(string path) => pullRequestsService.GetEncoding(path);

/// <summary>
/// Gets the full path to a file in the working directory.
/// </summary>
Expand Down
23 changes: 14 additions & 9 deletions src/GitHub.Exports.Reactive/Services/IPullRequestService.cs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
using System;
using System.Reactive;
using System.Text;
using GitHub.Models;
using LibGit2Sharp;
using Octokit;
Expand DownExpand Up@@ -121,23 +122,27 @@ IObservable<IPullRequestModel> CreatePullRequest(IRepositoryHost host,
IObservable<Tuple<string, int>> GetPullRequestForCurrentBranch(ILocalRepositoryModel repository);

/// <summary>
/// Gets the left and right files for a diff.
/// Gets the encoding for the specified file.
/// </summary>
/// <param name="path">The path to the file.</param>
/// <returns>The file's encoding</returns>
Encoding GetEncoding(string path);

/// <summary>
/// Gets a file as it appears in a pull request.
/// </summary>
/// <param name="repository">The repository.</param>
/// <param name="modelService">A model service to use as a cache if the file is not fetched.</param>
/// <param name="pullRequest">The pull request details.</param>
/// <param name="fileName">The filename relative to the repository root.</param>
/// <param name="fileSha">The SHA of the file in the pull request.</param>
/// <param name="isPullRequestBranchCheckedOut">
/// Whether the pull request branch is currently checked out. If so the right file returned
/// will be the path to the file in the working directory.
/// </param>
/// <param name="head">If true, gets the file at the PR head, otherwise gets the file at the PR base.</param>
/// <param name="encoding">The encoding to use.</param>
/// <returns>The paths of the left and right files for the diff.</returns>
IObservable<Tuple<string, string>> ExtractDiffFiles(
IObservable<string> ExtractFile(
ILocalRepositoryModel repository,
IPullRequestModel pullRequest,
string fileName,
bool isPullRequestBranchCheckedOut);
bool head,
Encoding encoding);

/// <summary>
/// Remotes all unused remotes that were created by GitHub for Visual Studio to track PRs
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Reactive;
using System.Text;
using System.Threading.Tasks;
using GitHub.Models;
using GitHub.Services;
Expand DownExpand Up@@ -161,21 +162,43 @@ public interface IPullRequestDetailViewModel : IViewModel, IHasLoading, IHasBusy
ReactiveCommand<object> OpenOnGitHub { get; }

/// <summary>
/// Gets a command that opens a <see cref="IPullRequestFileNode"/>.
/// Gets a command that diffs an <see cref="IPullRequestFileNode"/> between BASE and HEAD.
/// </summary>
ReactiveCommand<object> OpenFile { get; }
ReactiveCommand<object> DiffFile { get; }

/// <summary>
/// Gets a command that diffs a <see cref="IPullRequestFileNode"/>.
/// Gets a command that diffs an <see cref="IPullRequestFileNode"/> between the version in
/// the working directory and HEAD.
/// </summary>
ReactiveCommand<object> DiffFile { get; }
ReactiveCommand<object> DiffFileWithWorkingDirectory { get; }

/// <summary>
/// Gets a command that opens an <see cref="IPullRequestFileNode"/> from disk.
/// </summary>
ReactiveCommand<object> OpenFileInWorkingDirectory { get; }

/// <summary>
/// Gets the before and after files needed for viewing a diff.
/// Gets a command that opens an <see cref="IPullRequestFileNode"/> as it appears in the PR.
/// </summary>
ReactiveCommand<object> ViewFile { get; }

/// <summary>
/// Gets a file as it appears in the pull request.
/// </summary>
/// <param name="file">The changed file.</param>
/// <returns>A tuple containing the full path to the before and after files.</returns>
Task<Tuple<string, string>> ExtractDiffFiles(IPullRequestFileNode file);
/// <param name="head">
/// If true, gets the file at the PR head, otherwise gets the file at the PR merge base.
/// </param>
/// <param name="encoding">The encoding to use.</param>
/// <returns>The path to a temporary file.</returns>
Task<string> ExtractFile(IPullRequestFileNode file, bool head, Encoding encoding);

/// <summary>
/// Gets the encoding for the specified file.
/// </summary>
/// <param name="path">The path to the file.</param>
/// <returns>The file's encoding</returns>
Encoding GetEncoding(string path);

/// <summary>
/// Gets the full path to a file in the working directory.
Expand Down
Loading