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

Commit 6d98cdd

Browse files
committed
Display a "local changes can be pushed" message.
If the local branch is the PR source branch and it is ahead of the remote PR.
1 parent 683e795 commit 6d98cdd

File tree

6 files changed

+75
-14
lines changed

6 files changed

+75
-14
lines changed

src/GitHub.App/Services/PullRequestService.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,23 @@ public IObservable<IBranch> GetLocalBranches(ILocalRepositoryModel repository, P
142142
return Observable.Defer(() =>
143143
{
144144
var repo = gitService.GetRepository(repository.LocalPath);
145-
var result = GetLocalBranchesInternal(repo, repository.CloneUrl, pullRequest).Select(x => new BranchModel(x, repository));
145+
var result = GetLocalBranchesInternal(repository, repo, pullRequest).Select(x => new BranchModel(x, repository));
146146
return result.ToObservable();
147147
});
148148
}
149149

150+
public bool IsPullRequestFromFork(ILocalRepositoryModel repository, PullRequest pullRequest)
151+
{
152+
var sourceUrl = new UriString(pullRequest.Head.Repository.CloneUrl);
153+
return sourceUrl.ToRepositoryUrl() != repository.CloneUrl.ToRepositoryUrl();
154+
}
155+
150156
public IObservable<Unit> SwitchToBranch(ILocalRepositoryModel repository, PullRequest pullRequest)
151157
{
152158
return Observable.Defer(async () =>
153159
{
154160
var repo = gitService.GetRepository(repository.LocalPath);
155-
var branchName = GetLocalBranchesInternal(repo, repository.CloneUrl, pullRequest).First();
161+
var branchName = GetLocalBranchesInternal(repository, repo, pullRequest).First();
156162

157163
await gitClient.Fetch(repo, "origin");
158164

@@ -200,11 +206,12 @@ async Task DoFetchAndCheckout(ILocalRepositoryModel repository, int pullRequestN
200206
await gitClient.SetConfig(repo, configKey, pullRequestNumber.ToString());
201207
}
202208

203-
IEnumerable<string> GetLocalBranchesInternal(IRepository repository, UriString cloneUrl, PullRequest pullRequest)
209+
IEnumerable<string> GetLocalBranchesInternal(
210+
ILocalRepositoryModel localRepository,
211+
IRepository repository,
212+
PullRequest pullRequest)
204213
{
205-
var sourceUrl = new UriString(pullRequest.Head.Repository.CloneUrl);
206-
207-
if (sourceUrl.ToRepositoryUrl() == cloneUrl.ToRepositoryUrl())
214+
if (!IsPullRequestFromFork(localRepository, pullRequest))
208215
{
209216
return new[] { pullRequest.Head.Ref };
210217
}

src/GitHub.App/ViewModels/PullRequestDetailViewModel.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public PullRequestDetailViewModel(
9393
var canCheckout = this.WhenAnyValue(
9494
x => x.CheckoutMode,
9595
x => x.CheckoutDisabledMessage,
96-
(mode, disabled) => mode != CheckoutMode.UpToDate && disabled == null);
96+
(mode, disabled) => mode != CheckoutMode.UpToDate && mode != CheckoutMode.Push && disabled == null);
9797
Checkout = ReactiveCommand.CreateAsyncObservable(canCheckout, DoCheckout);
9898

9999
OpenOnGitHub = ReactiveCommand.Create();
@@ -337,10 +337,15 @@ public async Task Load(PullRequest pullRequest, IList<PullRequestFile> files)
337337
{
338338
var divergence = await pullRequestsService.CalculateHistoryDivergence(repository, Number);
339339

340-
if (divergence.BehindBy == null || divergence.AheadBy > 0)
340+
if (divergence.BehindBy == null)
341341
{
342342
CheckoutMode = CheckoutMode.InvalidState;
343343
}
344+
else if (divergence.AheadBy > 0)
345+
{
346+
CheckoutMode = pullRequestsService.IsPullRequestFromFork(repository, pullRequest) ?
347+
CheckoutMode.InvalidState : CheckoutMode.Push;
348+
}
344349
else if (divergence.BehindBy == 0)
345350
{
346351
CheckoutMode = CheckoutMode.UpToDate;

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ IObservable<IPullRequestModel> CreatePullRequest(IRepositoryHost host,
4545
/// <returns></returns>
4646
IObservable<IBranch> GetLocalBranches(ILocalRepositoryModel repository, PullRequest pullRequest);
4747

48+
/// <summary>
49+
/// Determines whether the specified pull request is from a fork.
50+
/// </summary>
51+
/// <param name="repository">The repository.</param>
52+
/// <param name="pullRequest">The octokit pull request details.</param>
53+
/// <returns></returns>
54+
bool IsPullRequestFromFork(ILocalRepositoryModel repository, PullRequest pullRequest);
55+
4856
/// <summary>
4957
/// Switches to an existing branch for the specified pull request.
5058
/// </summary>

src/GitHub.Exports.Reactive/ViewModels/IPullRequestDetailViewModel.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,14 @@ public enum CheckoutMode
6363
Fetch,
6464

6565
/// <summary>
66-
/// The checked out branch is marked as the branch for the pull request, but there are
67-
/// local commits or the branch shares no history.
66+
/// The checked out branch is the branch from which the pull request comes, and the are
67+
/// local changes, so invite the user to push.
68+
/// </summary>
69+
Push,
70+
71+
/// <summary>
72+
/// The checked out branch is marked as the branch for a pull request from a fork, but
73+
/// there are local commits or the branch shares no history.
6874
/// </summary>
6975
InvalidState,
7076
}

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@
1919
<d:DesignProperties.DataContext>
2020
<Binding>
2121
<Binding.Source>
22-
<sampleData:PullRequestDetailViewModelDesigner CheckoutMode="NeedsPull"
23-
CommitsBehind="2"
24-
CheckoutDisabledMessage="Cannot update branch as your working directory has uncommitted changes."/>
22+
<sampleData:PullRequestDetailViewModelDesigner CheckoutMode="Push"
23+
CommitsBehind="2"/>
2524
</Binding.Source>
2625
</Binding>
2726
</d:DesignProperties.DataContext>
@@ -238,6 +237,22 @@
238237
</TextBlock.Style>
239238
</TextBlock>
240239

240+
<!-- Local branch is checked out but needs pushing-->
241+
<TextBlock TextWrapping="Wrap">
242+
<ui:OcticonImage Icon="info" Foreground="Orange" Margin="0 0 0 -4"/>
243+
<Run BaselineAlignment="Top">Local changes can be pushed.</Run>
244+
<TextBlock.Style>
245+
<Style TargetType="TextBlock" BasedOn="{StaticResource CheckoutMessage}">
246+
<Setter Property="Visibility" Value="Collapsed"/>
247+
<Style.Triggers>
248+
<DataTrigger Binding="{Binding CheckoutMode}" Value="Push">
249+
<Setter Property="Visibility" Value="Visible"/>
250+
</DataTrigger>
251+
</Style.Triggers>
252+
</Style>
253+
</TextBlock.Style>
254+
</TextBlock>
255+
241256
<!-- Checkout disabled message -->
242257
<TextBlock Margin="0 4" TextWrapping="Wrap">
243258
<ui:OcticonImage Icon="alert" Foreground="Orange" Margin="0 0 0 -4"/>

src/UnitTests/GitHub.App/ViewModels/PullRequestDetailViewModelTests.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,26 @@ public async Task CheckoutDisabledMessageShouldBeSetWhenNeedsFetchAndWorkingDire
164164
}
165165

166166
[Fact]
167-
public async Task CheckoutModeShouldBeInvalidStateWhenHasLocalCommits()
167+
public async Task CheckoutModeShouldBePush()
168168
{
169169
var target = CreateTarget(
170170
currentBranch: "pr/123",
171171
existingPrBranch: "pr/123",
172+
prFromFork: false,
173+
aheadBy: 1);
174+
await target.Load(CreatePullRequest(), new PullRequestFile[0]);
175+
176+
Assert.Equal(CheckoutMode.Push, target.CheckoutMode);
177+
Assert.False(target.Checkout.CanExecute(null));
178+
}
179+
180+
[Fact]
181+
public async Task CheckoutModeShouldBeInvalidStateWhenBranchFromForkHasLocalCommits()
182+
{
183+
var target = CreateTarget(
184+
currentBranch: "pr/123",
185+
existingPrBranch: "pr/123",
186+
prFromFork: true,
172187
aheadBy: 1,
173188
behindBy: 2);
174189
await target.Load(CreatePullRequest(), new PullRequestFile[0]);
@@ -183,6 +198,7 @@ public async Task CheckoutDisabledMessageShouldBeSetWhenInvalidStateAndWorkingDi
183198
var target = CreateTarget(
184199
currentBranch: "pr/123",
185200
existingPrBranch: "pr/123",
201+
prFromFork: true,
186202
aheadBy: 1,
187203
behindBy: 2,
188204
dirty: true);
@@ -267,13 +283,15 @@ public async Task CheckoutInvalidStateShouldCallService()
267283
PullRequestDetailViewModel CreateTarget(
268284
string currentBranch = "master",
269285
string existingPrBranch = null,
286+
bool prFromFork = false,
270287
bool dirty = false,
271288
int aheadBy = 0,
272289
int behindBy = 0)
273290
{
274291
return CreateTargetAndService(
275292
currentBranch: currentBranch,
276293
existingPrBranch: existingPrBranch,
294+
prFromFork: prFromFork,
277295
dirty: dirty,
278296
aheadBy: aheadBy,
279297
behindBy: behindBy).Item1;
@@ -282,6 +300,7 @@ PullRequestDetailViewModel CreateTarget(
282300
Tuple<PullRequestDetailViewModel, IPullRequestService> CreateTargetAndService(
283301
string currentBranch = "master",
284302
string existingPrBranch = null,
303+
bool prFromFork = false,
285304
bool dirty = false,
286305
int aheadBy = 0,
287306
int behindBy = 0)
@@ -305,6 +324,7 @@ Tuple<PullRequestDetailViewModel, IPullRequestService> CreateTargetAndService(
305324
.Returns(Observable.Empty<IBranch>());
306325
}
307326

327+
pullRequestService.IsPullRequestFromFork(repository, Arg.Any<PullRequest>()).Returns(prFromFork);
308328
pullRequestService.CleanForCheckout(repository).Returns(Observable.Return(!dirty));
309329

310330
var divergence = Substitute.For<HistoryDivergence>();

0 commit comments

Comments
 (0)