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

Commit 4a4d4e5

Browse files
authored
Merge pull request #1392 from github/feature/submodule-changes
[Feature] Reviewing PR with submodule changes
2 parents c519e89 + 3ea7ea7 commit 4a4d4e5

File tree

11 files changed

+594
-20
lines changed

11 files changed

+594
-20
lines changed

src/GitHub.App/Resources.Designer.cs

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/GitHub.App/Resources.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,4 +285,13 @@
285285
<data name="WorkingDirectoryHasUncommittedCHanges" xml:space="preserve">
286286
<value>Cannot checkout as your working directory has uncommitted changes.</value>
287287
</data>
288+
<data name="SyncSubmodules" xml:space="preserve">
289+
<value>Sync {0} submodules</value>
290+
</data>
291+
<data name="CouldntFindGitOnPath" xml:space="preserve">
292+
<value>Couldn't find Git.exe on PATH.
293+
294+
Please install Git for Windows from:
295+
https://git-scm.com/download/win</value>
296+
</data>
288297
</root>

src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ public PullRequestDetailViewModelDesigner()
9494
public ReactiveCommand<Unit> Checkout { get; }
9595
public ReactiveCommand<Unit> Pull { get; }
9696
public ReactiveCommand<Unit> Push { get; }
97+
public ReactiveCommand<Unit> SyncSubmodules { get; }
9798
public ReactiveCommand<object> OpenOnGitHub { get; }
9899
public ReactiveCommand<object> DiffFile { get; }
99100
public ReactiveCommand<object> DiffFileWithWorkingDirectory { get; }

src/GitHub.App/Services/PullRequestService.cs

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.ComponentModel.Composition;
33
using System.IO;
44
using System.Linq;
5+
using System.Diagnostics;
56
using GitHub.Models;
67
using System.Reactive.Linq;
78
using Rothko;
@@ -103,13 +104,44 @@ public IObservable<IReadOnlyList<CommitMessage>> GetMessagesForUniqueCommits(
103104
});
104105
}
105106

107+
public IObservable<int> CountSubmodulesToSync(ILocalRepositoryModel repository)
108+
{
109+
using (var repo = gitService.GetRepository(repository.LocalPath))
110+
{
111+
var count = 0;
112+
foreach (var submodule in repo.Submodules)
113+
{
114+
var status = submodule.RetrieveStatus();
115+
if ((status & SubmoduleStatus.WorkDirAdded) != 0)
116+
{
117+
count++;
118+
}
119+
else if ((status & SubmoduleStatus.WorkDirDeleted) != 0)
120+
{
121+
count++;
122+
}
123+
else if ((status & SubmoduleStatus.WorkDirModified) != 0)
124+
{
125+
count++;
126+
}
127+
else if ((status & SubmoduleStatus.WorkDirUninitialized) != 0)
128+
{
129+
count++;
130+
}
131+
}
132+
133+
return Observable.Return(count);
134+
}
135+
}
136+
106137
public IObservable<bool> IsWorkingDirectoryClean(ILocalRepositoryModel repository)
107138
{
108139
// The `using` appears to resolve this issue:
109140
// https://github.com/github/VisualStudio/issues/1306
110141
using (var repo = gitService.GetRepository(repository.LocalPath))
111142
{
112-
var isClean = !IsFilthy(repo.RetrieveStatus());
143+
var statusOptions = new StatusOptions { ExcludeSubmodules = true };
144+
var isClean = !IsFilthy(repo.RetrieveStatus(statusOptions));
113145
return Observable.Return(isClean);
114146
}
115147
}
@@ -154,6 +186,69 @@ public IObservable<Unit> Push(ILocalRepositoryModel repository)
154186
});
155187
}
156188

189+
public async Task<bool> SyncSubmodules(ILocalRepositoryModel repository, Action<string> progress)
190+
{
191+
var exitCode = await Where("git");
192+
if (exitCode != 0)
193+
{
194+
progress(App.Resources.CouldntFindGitOnPath);
195+
return false;
196+
}
197+
198+
return await SyncSubmodules(repository.LocalPath, progress) == 0;
199+
}
200+
201+
// LibGit2Sharp has limited submodule support so shelling out Git.exe for submodule commands.
202+
async Task<int> SyncSubmodules(string workingDir, Action<string> progress)
203+
{
204+
var cmdArguments = "/C git submodule init & git submodule sync --recursive & git submodule update --recursive";
205+
var startInfo = new ProcessStartInfo("cmd", cmdArguments)
206+
{
207+
WorkingDirectory = workingDir,
208+
UseShellExecute = false,
209+
CreateNoWindow = true,
210+
RedirectStandardOutput = true,
211+
RedirectStandardError = true
212+
};
213+
214+
using (var process = Process.Start(startInfo))
215+
{
216+
await Task.WhenAll(
217+
ReadLinesAsync(process.StandardOutput, progress),
218+
ReadLinesAsync(process.StandardError, progress),
219+
Task.Run(() => process.WaitForExit()));
220+
return process.ExitCode;
221+
}
222+
}
223+
224+
static Task<int> Where(string fileName)
225+
{
226+
return Task.Run(() =>
227+
{
228+
var cmdArguments = "/C WHERE /Q " + fileName;
229+
var startInfo = new ProcessStartInfo("cmd", cmdArguments)
230+
{
231+
UseShellExecute = false,
232+
CreateNoWindow = true
233+
};
234+
235+
using (var process = Process.Start(startInfo))
236+
{
237+
process.WaitForExit();
238+
return process.ExitCode;
239+
}
240+
});
241+
}
242+
243+
static async Task ReadLinesAsync(TextReader reader, Action<string> progress)
244+
{
245+
string line;
246+
while ((line = await reader.ReadLineAsync()) != null)
247+
{
248+
progress(line);
249+
}
250+
}
251+
157252
public IObservable<Unit> Checkout(ILocalRepositoryModel repository, IPullRequestModel pullRequest, string localBranchName)
158253
{
159254
return Observable.Defer(async () =>

src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Reactive.Linq;
88
using System.Reactive.Threading.Tasks;
99
using System.Threading.Tasks;
10+
using System.Globalization;
1011
using GitHub.App;
1112
using GitHub.Extensions;
1213
using GitHub.Factories;
@@ -102,6 +103,14 @@ public PullRequestDetailViewModel(
102103
DoPush);
103104
SubscribeOperationError(Push);
104105

106+
SyncSubmodules = ReactiveCommand.CreateAsyncTask(
107+
this.WhenAnyValue(x => x.UpdateState)
108+
.Cast<UpdateCommandState>()
109+
.Select(x => x != null && x.SyncSubmodulesEnabled),
110+
DoSyncSubmodules);
111+
SyncSubmodules.Subscribe(_ => Refresh().ToObservable());
112+
SubscribeOperationError(SyncSubmodules);
113+
105114
OpenOnGitHub = ReactiveCommand.Create();
106115
DiffFile = ReactiveCommand.Create();
107116
DiffFileWithWorkingDirectory = ReactiveCommand.Create(this.WhenAnyValue(x => x.IsCheckedOut));
@@ -267,6 +276,11 @@ public Uri WebUrl
267276
/// </summary>
268277
public ReactiveCommand<Unit> Push { get; }
269278

279+
/// <summary>
280+
/// Sync submodules for PR branch.
281+
/// </summary>
282+
public ReactiveCommand<Unit> SyncSubmodules { get; }
283+
270284
/// <summary>
271285
/// Gets a command that opens the pull request on GitHub.
272286
/// </summary>
@@ -407,7 +421,10 @@ public async Task Load(IPullRequestModel pullRequest)
407421
pushToolTip = Resources.MustPullBeforePush;
408422
}
409423

410-
UpdateState = new UpdateCommandState(divergence, pullEnabled, pushEnabled, pullToolTip, pushToolTip);
424+
var submodulesToSync = await pullRequestsService.CountSubmodulesToSync(LocalRepository);
425+
var syncSubmodulesToolTip = string.Format(Resources.SyncSubmodules, submodulesToSync);
426+
427+
UpdateState = new UpdateCommandState(divergence, pullEnabled, pushEnabled, pullToolTip, pushToolTip, syncSubmodulesToolTip, submodulesToSync);
411428
CheckoutState = null;
412429
}
413430
else
@@ -659,6 +676,26 @@ IObservable<Unit> DoPush(object unused)
659676
});
660677
}
661678

679+
async Task DoSyncSubmodules(object unused)
680+
{
681+
try
682+
{
683+
IsBusy = true;
684+
usageTracker.IncrementCounter(x => x.NumberOfSyncSubmodules).Forget();
685+
686+
var writer = new StringWriter(CultureInfo.CurrentCulture);
687+
var complete = await pullRequestsService.SyncSubmodules(LocalRepository, writer.WriteLine);
688+
if (!complete)
689+
{
690+
throw new ApplicationException(writer.ToString());
691+
}
692+
}
693+
finally
694+
{
695+
IsBusy = false;
696+
}
697+
}
698+
662699
class CheckoutCommandState : IPullRequestCheckoutState
663700
{
664701
public CheckoutCommandState(string caption, string disabledMessage)
@@ -680,23 +717,30 @@ public UpdateCommandState(
680717
bool pullEnabled,
681718
bool pushEnabled,
682719
string pullToolTip,
683-
string pushToolTip)
720+
string pushToolTip,
721+
string syncSubmodulesToolTip,
722+
int submodulesToSync)
684723
{
685724
CommitsAhead = divergence.AheadBy ?? 0;
686725
CommitsBehind = divergence.BehindBy ?? 0;
687726
PushEnabled = pushEnabled;
688727
PullEnabled = pullEnabled;
689728
PullToolTip = pullToolTip;
690729
PushToolTip = pushToolTip;
730+
SyncSubmodulesToolTip = syncSubmodulesToolTip;
731+
SubmodulesToSync = submodulesToSync;
691732
}
692733

693734
public int CommitsAhead { get; }
694735
public int CommitsBehind { get; }
695-
public bool UpToDate => CommitsAhead == 0 && CommitsBehind == 0;
736+
public bool UpToDate => CommitsAhead == 0 && CommitsBehind == 0 && !SyncSubmodulesEnabled;
696737
public bool PullEnabled { get; }
697738
public bool PushEnabled { get; }
739+
public bool SyncSubmodulesEnabled => SubmodulesToSync > 0;
698740
public string PullToolTip { get; }
699741
public string PushToolTip { get; }
742+
public string SyncSubmodulesToolTip { get; }
743+
public int SubmodulesToSync { get; }
700744
}
701745
}
702746
}

src/GitHub.Exports.Reactive/Services/IPullRequestService.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ IObservable<IPullRequestModel> CreatePullRequest(IModelService modelService,
2424
/// <returns></returns>
2525
IObservable<bool> IsWorkingDirectoryClean(ILocalRepositoryModel repository);
2626

27+
/// <summary>
28+
/// Count the number of submodules that require syncing.
29+
/// </summary>
30+
/// <param name="repository">The repository.</param>
31+
/// <returns>The number of submodules that need to be synced.</returns>
32+
IObservable<int> CountSubmodulesToSync(ILocalRepositoryModel repository);
33+
2734
/// <summary>
2835
/// Checks out a pull request to a local branch.
2936
/// </summary>
@@ -45,6 +52,12 @@ IObservable<IPullRequestModel> CreatePullRequest(IModelService modelService,
4552
/// <param name="repository">The repository.</param>
4653
IObservable<Unit> Push(ILocalRepositoryModel repository);
4754

55+
/// <summary>
56+
/// Sync submodules on the current branch.
57+
/// </summary>
58+
/// <param name="repository">The repository.</param>
59+
Task<bool> SyncSubmodules(ILocalRepositoryModel repository, Action<string> progress);
60+
4861
/// <summary>
4962
/// Calculates the name of a local branch for a pull request avoiding clashes with existing branches.
5063
/// </summary>

src/GitHub.Exports/Models/UsageModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public class UsageModel
3030
public int NumberOfForkPullRequestsCheckedOut { get; set; }
3131
public int NumberOfForkPullRequestPulls { get; set; }
3232
public int NumberOfForkPullRequestPushes { get; set; }
33+
public int NumberOfSyncSubmodules { get; set; }
3334
public int NumberOfWelcomeDocsClicks { get; set; }
3435
public int NumberOfWelcomeTrainingClicks { get; set; }
3536
public int NumberOfGitHubPaneHelpClicks { get; set; }

src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,16 @@
183183
ToolTip="{Binding UpdateState.PushToolTip}"
184184
ToolTipService.ShowOnDisabled="True"
185185
VerticalAlignment="Center"/>
186+
<!-- Sync submodules -->
187+
<ui:OcticonImage Icon="package"
188+
Visibility="{Binding UpdateState.SyncSubmodulesEnabled, FallbackValue=Collapsed, Converter={ui:BooleanToVisibilityConverter}}" />
189+
<TextBlock Margin="4 0 0 0" Text="{Binding UpdateState.SubmodulesToSync}" VerticalAlignment="Center"
190+
Visibility="{Binding UpdateState.SyncSubmodulesEnabled, FallbackValue=Collapsed, Converter={ui:BooleanToVisibilityConverter}}" />
191+
<ui:GitHubActionLink Content="Sync"
192+
Command="{Binding SyncSubmodules}"
193+
Margin="4 0"
194+
ToolTip="{Binding UpdateState.SyncSubmodulesToolTip}"
195+
Visibility="{Binding UpdateState.SyncSubmodulesEnabled, FallbackValue=Collapsed, Converter={ui:BooleanToVisibilityConverter}}" />
186196
</StackPanel>
187197

188198
<!-- Branch checked out and up-to-date -->

0 commit comments

Comments
 (0)