Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit d8f9e87

Browse files
Merge branch 'master' into pull-request-status-circle
2 parents a4d9dfb + 285c664 commit d8f9e87

File tree

28 files changed

+207
-169
lines changed

28 files changed

+207
-169
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ If you have an existing clone, make sure to run `git submodule sync` to update y
1212

1313
## About
1414

15-
The GitHub Extension for Visual Studio provides GitHub integration in Visual Studio 2015.
15+
The GitHub Extension for Visual Studio provides GitHub integration in Visual Studio 2015 and newer.
1616
Most of the extension UI lives in the Team Explorer pane, which is available from the View menu.
1717

1818
Official builds of this extension are available at [the official website](https://visualstudio.github.com).

src/GitHub.App/GitHub.App.csproj

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,18 @@
4646
<PackageReference Include="LibGit2Sharp.NativeBinaries" Version="1.0.164" />
4747
<PackageReference Include="Madskristensen.VisualStudio.SDK" Version="14.3.75-pre" />
4848
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.1" />
49+
<PackageReference Include="Microsoft.VisualStudio.StaticReviews.Embeddable" Version="0.1.14-alpha" />
4950
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
5051
<PackageReference Include="Rothko" Version="0.0.3-ghfvs" />
5152
<PackageReference Include="Serilog" Version="2.5.0" />
5253
<PackageReference Include="SerilogAnalyzer" Version="0.12.0.0" />
5354
<PackageReference Include="Stateless" Version="2.5.56.0" targetFramework="net45" />
5455
</ItemGroup>
55-
</Project>
56+
<Target Name="LinkStaticReviewsEmbeddableAssemblies" AfterTargets="ResolveReferences" BeforeTargets="FindReferenceAssembliesForReferences">
57+
<ItemGroup>
58+
<ReferencePath Condition="'%(FileName)' == 'Microsoft.VisualStudio.StaticReviews.Embeddable'">
59+
<EmbedInteropTypes>true</EmbedInteropTypes>
60+
</ReferencePath>
61+
</ItemGroup>
62+
</Target>
63+
</Project>

src/GitHub.App/SampleData/SampleViewModels.cs

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ public RepositoryCreationViewModelDesigner()
2929
RepositoryName = "Hello-World";
3030
Description = "A description";
3131
KeepPrivate = true;
32-
CanKeepPrivate = true;
3332
Accounts = new ReactiveList<IAccount>
3433
{
3534
new AccountDesigner { Login = "shana" },
@@ -80,12 +79,6 @@ public ReactiveCommand<Unit, Unit> BrowseForDirectory
8079
private set;
8180
}
8281

83-
public bool CanKeepPrivate
84-
{
85-
get;
86-
private set;
87-
}
88-
8982
public ReactiveCommand<Unit, Unit> CreateRepository
9083
{
9184
get;
@@ -146,24 +139,6 @@ public IAccount SelectedAccount
146139
set;
147140
}
148141

149-
public bool ShowUpgradePlanWarning
150-
{
151-
get;
152-
private set;
153-
}
154-
155-
public bool ShowUpgradeToMicroPlanWarning
156-
{
157-
get;
158-
private set;
159-
}
160-
161-
public ICommand UpgradeAccountPlan
162-
{
163-
get;
164-
private set;
165-
}
166-
167142
public IReadOnlyList<GitIgnoreItem> GitIgnoreTemplates
168143
{
169144
get; private set;

src/GitHub.App/Services/PullRequestService.cs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
using System.Reactive;
99
using System.Reactive.Linq;
1010
using System.Reactive.Threading.Tasks;
11+
using System.Runtime.InteropServices;
1112
using System.Text;
1213
using System.Text.RegularExpressions;
14+
using System.Threading;
1315
using System.Threading.Tasks;
1416
using System.Windows.Forms;
1517
using GitHub.Api;
@@ -19,6 +21,7 @@
1921
using GitHub.Models;
2022
using GitHub.Primitives;
2123
using LibGit2Sharp;
24+
using Microsoft.VisualStudio.StaticReviews.Contracts;
2225
using Octokit.GraphQL;
2326
using Octokit.GraphQL.Model;
2427
using Rothko;
@@ -32,7 +35,7 @@ namespace GitHub.Services
3235
{
3336
[Export(typeof(IPullRequestService))]
3437
[PartCreationPolicy(CreationPolicy.Shared)]
35-
public class PullRequestService : IPullRequestService
38+
public class PullRequestService : IPullRequestService, IStaticReviewFileMap
3639
{
3740
const string SettingCreatedByGHfVS = "created-by-ghfvs";
3841
const string SettingGHfVSPullRequest = "ghfvs-pr-owner-number";
@@ -58,6 +61,8 @@ public class PullRequestService : IPullRequestService
5861
readonly IOperatingSystem os;
5962
readonly IUsageTracker usageTracker;
6063

64+
readonly IDictionary<string, (string commitId, string repoPath)> tempFileMappings;
65+
6166
[ImportingConstructor]
6267
public PullRequestService(
6368
IGitClient gitClient,
@@ -73,6 +78,7 @@ public PullRequestService(
7378
this.graphqlFactory = graphqlFactory;
7479
this.os = os;
7580
this.usageTracker = usageTracker;
81+
this.tempFileMappings = new Dictionary<string, (string commitId, string repoPath)>(StringComparer.OrdinalIgnoreCase);
7682
}
7783

7884
public async Task<Page<PullRequestListItemModel>> ReadPullRequests(
@@ -756,6 +762,12 @@ public async Task<string> ExtractToTempFile(
756762
}
757763
}
758764

765+
lock (this.tempFileMappings)
766+
{
767+
string gitRelativePath = relativePath.TrimStart('/').Replace('\\', '/');
768+
this.tempFileMappings[CanonicalizeLocalFilePath(tempFilePath)] = (commitSha, gitRelativePath);
769+
}
770+
759771
return tempFilePath;
760772
}
761773

@@ -828,6 +840,28 @@ public bool ConfirmCancelPendingReview()
828840
MessageBoxIcon.Question) == DialogResult.Yes;
829841
}
830842

843+
/// <inheritdoc />
844+
public Task<string> GetObjectishFromLocalPathAsync(string localPath, CancellationToken cancellationToken)
845+
{
846+
lock (this.tempFileMappings)
847+
{
848+
var canonicalizedPath = CanonicalizeLocalFilePath(localPath);
849+
if (this.tempFileMappings.TryGetValue(canonicalizedPath, out (string commitId, string repoPath) result))
850+
{
851+
return Task.FromResult($"{result.commitId}:{result.repoPath}");
852+
}
853+
}
854+
855+
return Task.FromResult<string>(null);
856+
}
857+
858+
/// <inheritdoc />
859+
public Task<string> GetLocalPathFromObjectishAsync(string objectish, CancellationToken cancellationToken)
860+
{
861+
throw new NotImplementedException();
862+
}
863+
864+
831865
async Task<string> CreateRemote(IRepository repo, UriString cloneUri)
832866
{
833867
foreach (var remote in repo.Network.Remotes)
@@ -1007,6 +1041,12 @@ static string BuildGHfVSConfigKeyValue(string owner, int number)
10071041
return default;
10081042
}
10091043

1044+
static string CanonicalizeLocalFilePath(string localPath)
1045+
{
1046+
localPath = localPath.Replace("\\\\", "\\");
1047+
return Path.GetFullPath(localPath);
1048+
}
1049+
10101050
class ListItemAdapter : PullRequestListItemModel
10111051
{
10121052
public IList<ReviewAdapter> Reviews { get; set; }

src/GitHub.App/Services/RepositoryCloneService.cs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.IO;
55
using System.Linq;
66
using System.Reactive.Linq;
7+
using System.Threading;
78
using System.Threading.Tasks;
89
using GitHub.Api;
910
using GitHub.Extensions;
@@ -69,13 +70,8 @@ public async Task<ViewerRepositoriesModel> ReadViewerRepositories(HostAddress ad
6970
{
7071
var order = new RepositoryOrder
7172
{
72-
Field = RepositoryOrderField.Name,
73-
Direction = OrderDirection.Asc
74-
};
75-
76-
var affiliation = new RepositoryAffiliation?[]
77-
{
78-
RepositoryAffiliation.Owner, RepositoryAffiliation.Collaborator
73+
Field = RepositoryOrderField.PushedAt,
74+
Direction = OrderDirection.Desc
7975
};
8076

8177
var repositorySelection = new Fragment<Repository, RepositoryListItemModel>(
@@ -94,14 +90,17 @@ public async Task<ViewerRepositoriesModel> ReadViewerRepositories(HostAddress ad
9490
.Select(viewer => new ViewerRepositoriesModel
9591
{
9692
Owner = viewer.Login,
97-
Repositories = viewer.Repositories(null, null, null, null, null, null, null, order, affiliation, null)
93+
Repositories = viewer.Repositories(null, null, null, null, null, null, null, order, null, null)
9894
.AllPages()
9995
.Select(repositorySelection).ToList(),
96+
ContributedToRepositories = viewer.RepositoriesContributedTo(100, null, null, null, null, null, null, order, null)
97+
.Nodes
98+
.Select(repositorySelection).ToList(),
10099
Organizations = viewer.Organizations(null, null, null, null).AllPages().Select(org => new
101100
{
102101
org.Login,
103-
Repositories = org.Repositories(null, null, null, null, null, null, null, order, null, null)
104-
.AllPages()
102+
Repositories = org.Repositories(100, null, null, null, null, null, null, order, null, null)
103+
.Nodes
105104
.Select(repositorySelection).ToList()
106105
}).ToDictionary(x => x.Login, x => (IReadOnlyList<RepositoryListItemModel>)x.Repositories),
107106
}).Compile();

src/GitHub.App/ViewModels/Dialog/Clone/RepositoryCloneViewModel.cs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ public async Task InitializeAsync(IConnection connection)
137137

138138
this.WhenAnyValue(x => x.SelectedTabIndex).Subscribe(x => tabs[x].Activate().Forget());
139139

140-
// When a clipboard URL has been set or a user is in group A, show the URL tab by default
141-
if (!string.IsNullOrEmpty(UrlTab.Url) || await IsGroupA().ConfigureAwait(false))
140+
// When a clipboard URL has been set, show the URL tab by default
141+
if (!string.IsNullOrEmpty(UrlTab.Url))
142142
{
143143
SelectedTabIndex = 2;
144144
}
@@ -157,14 +157,6 @@ public async Task InitializeAsync(IConnection connection)
157157
}
158158
}
159159

160-
// Put 50% of users in group A
161-
async Task<bool> IsGroupA()
162-
{
163-
var userGuid = await usageService.GetUserGuid().ConfigureAwait(false);
164-
var lastByte = userGuid.ToByteArray().Last();
165-
return lastByte % 2 == 0;
166-
}
167-
168160
void BrowseForDirectory()
169161
{
170162
var result = os.Dialog.BrowseForDirectory(Path, Resources.BrowseForDirectory);

src/GitHub.App/ViewModels/Dialog/Clone/RepositorySelectViewModel.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.ComponentModel;
44
using System.ComponentModel.Composition;
5+
using System.Globalization;
56
using System.Linq;
67
using System.Reactive.Linq;
78
using System.Threading.Tasks;
@@ -108,7 +109,8 @@ public async Task Activate()
108109

109110
try
110111
{
111-
var results = await service.ReadViewerRepositories(connection.HostAddress).ConfigureAwait(true);
112+
var results = await log.TimeAsync(nameof(service.ReadViewerRepositories),
113+
() => service.ReadViewerRepositories(connection.HostAddress));
112114

113115
var yourRepositories = results.Repositories
114116
.Where(r => r.Owner == results.Owner)
@@ -117,10 +119,17 @@ public async Task Activate()
117119
.Where(r => r.Owner != results.Owner)
118120
.OrderBy(r => r.Owner)
119121
.Select(x => new RepositoryItemViewModel(x, "Collaborator repositories"));
122+
var repositoriesContributedTo = results.ContributedToRepositories
123+
.Select(x => new RepositoryItemViewModel(x, "Contributed to repositories"));
120124
var orgRepositories = results.Organizations
121125
.OrderBy(x => x.Key)
122-
.SelectMany(x => x.Value.Select(y => new RepositoryItemViewModel(y, x.Key)));
123-
Items = yourRepositories.Concat(collaboratorRepositories).Concat(orgRepositories).ToList();
126+
.SelectMany(x => x.Value.Select(y => new RepositoryItemViewModel(y, GroupName(x, 100))));
127+
Items = yourRepositories
128+
.Concat(collaboratorRepositories)
129+
.Concat(repositoriesContributedTo)
130+
.Concat(orgRepositories)
131+
.ToList();
132+
log.Information("Read {Total} viewer repositories", Items.Count);
124133
ItemsView = CollectionViewSource.GetDefaultView(Items);
125134
ItemsView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(RepositoryItemViewModel.Group)));
126135
ItemsView.Filter = FilterItem;
@@ -142,6 +151,17 @@ public async Task Activate()
142151
}
143152
}
144153

154+
static string GroupName(KeyValuePair<string, IReadOnlyList<RepositoryListItemModel>> group, int max)
155+
{
156+
var name = group.Key;
157+
if (group.Value.Count == max)
158+
{
159+
name += $" ({string.Format(CultureInfo.InvariantCulture, Resources.MostRecentlyPushed, max)})";
160+
}
161+
162+
return name;
163+
}
164+
145165
bool FilterItem(object obj)
146166
{
147167
if (obj is IRepositoryItemViewModel item && !string.IsNullOrWhiteSpace(Filter))

src/GitHub.App/ViewModels/Dialog/RepositoryCreationViewModel.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ public class RepositoryCreationViewModel : RepositoryFormViewModel, IRepositoryC
3939
readonly IModelServiceFactory modelServiceFactory;
4040
readonly IRepositoryCreationService repositoryCreationService;
4141
readonly ObservableAsPropertyHelper<bool> isCreating;
42-
readonly ObservableAsPropertyHelper<bool> canKeepPrivate;
4342
readonly IOperatingSystem operatingSystem;
4443
readonly IUsageTracker usageTracker;
4544
ObservableAsPropertyHelper<IReadOnlyList<IAccount>> accounts;
@@ -96,10 +95,6 @@ public RepositoryCreationViewModel(
9695

9796
CreateRepository = InitializeCreateRepositoryCommand();
9897

99-
canKeepPrivate = CanKeepPrivateObservable.CombineLatest(CreateRepository.IsExecuting,
100-
(canKeep, publishing) => canKeep && !publishing)
101-
.ToProperty(this, x => x.CanKeepPrivate);
102-
10398
isCreating = CreateRepository.IsExecuting
10499
.ToProperty(this, x => x.IsCreating);
105100

@@ -128,11 +123,6 @@ public string BaseRepositoryPath
128123
/// </summary>
129124
public bool IsCreating { get { return isCreating.Value; } }
130125

131-
/// <summary>
132-
/// If the repo can be made private (depends on the user plan)
133-
/// </summary>
134-
public bool CanKeepPrivate { get { return canKeepPrivate.Value; } }
135-
136126
IReadOnlyList<GitIgnoreItem> gitIgnoreTemplates;
137127
public IReadOnlyList<GitIgnoreItem> GitIgnoreTemplates
138128
{

0 commit comments

Comments
 (0)