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

Commit 04d89a2

Browse files
authored
Merge pull request #373 from github/paladique/pick-dir-validation
[Backport] Add directory validation
2 parents a867b3e + 5d08171 commit 04d89a2

File tree

8 files changed

+76
-42
lines changed

8 files changed

+76
-42
lines changed

src/GitHub.App/Extensions/ValidationExtensions.cs

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/GitHub.App/GitHub.App.csproj

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@
117117
<Compile Include="Caches\ImageCache.cs" />
118118
<Compile Include="Extensions\AkavacheExtensions.cs" />
119119
<Compile Include="Extensions\EnvironmentExtensions.cs" />
120-
<Compile Include="Extensions\ValidationExtensions.cs" />
121120
<Compile Include="GlobalSuppressions.cs" />
122121
<Compile Include="Infrastructure\LoggingConfiguration.cs" />
123122
<Compile Include="Models\PullRequestModel.cs" />
@@ -282,11 +281,11 @@
282281
</Target>
283282
<Import Project="..\..\packages\Fody.1.28.0\build\Fody.targets" Condition="Exists('..\..\packages\Fody.1.28.0\build\Fody.targets')" />
284283
<Import Project="..\..\packages\SQLitePCL.raw_basic.0.7.3.0-vs2012\build\net45\SQLitePCL.raw_basic.targets" Condition="Exists('..\..\packages\SQLitePCL.raw_basic.0.7.3.0-vs2012\build\net45\SQLitePCL.raw_basic.targets')" />
285-
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
284+
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
286285
Other similar extension points exist, see Microsoft.Common.targets.
287286
<Target Name="BeforeBuild">
288287
</Target>
289288
<Target Name="AfterBuild">
290289
</Target>
291290
-->
292-
</Project>
291+
</Project>

src/GitHub.App/SampleData/SampleViewModels.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,11 @@ public RepositoryCloneViewModelDesigner()
447447
x => x
448448
);
449449

450-
BaseRepositoryPathValidator = this.CreateBaseRepositoryPathValidator();
450+
BaseRepositoryPathValidator = ReactivePropertyValidator.ForObservable(this.WhenAny(x => x.BaseRepositoryPath, x => x.Value))
451+
.IfNullOrEmpty("Please enter a repository path")
452+
.IfTrue(x => x.Length > 200, "Path too long")
453+
.IfContainsInvalidPathChars("Path contains invalid characters")
454+
.IfPathNotRooted("Please enter a valid path");
451455
}
452456

453457
public IReactiveCommand<Unit> CloneCommand

src/GitHub.App/ViewModels/RepositoryCloneViewModel.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
using System.ComponentModel.Composition;
44
using System.Diagnostics;
55
using System.Globalization;
6+
using System.IO;
7+
using System.Linq;
68
using System.Reactive;
79
using System.Reactive.Linq;
10+
using System.Text.RegularExpressions;
811
using System.Windows.Input;
912
using GitHub.App;
1013
using GitHub.Exports;
@@ -80,7 +83,17 @@ public RepositoryCloneViewModel(
8083
signalReset: filterResetSignal
8184
);
8285

83-
BaseRepositoryPathValidator = this.CreateBaseRepositoryPathValidator();
86+
var baseRepositoryPath = this.WhenAny(
87+
x => x.BaseRepositoryPath,
88+
x => x.SelectedRepository,
89+
(x, y) => x.Value);
90+
91+
BaseRepositoryPathValidator = ReactivePropertyValidator.ForObservable(baseRepositoryPath)
92+
.IfNullOrEmpty("Please enter a repository path")
93+
.IfTrue(x => x.Length > 200, "Path too long")
94+
.IfContainsInvalidPathChars("Path contains invalid characters")
95+
.IfPathNotRooted("Please enter a valid path")
96+
.IfTrue(IsAlreadyRepoAtPath, Resources.RepositoryNameValidatorAlreadyExists);
8497

8598
var canCloneObservable = this.WhenAny(
8699
x => x.SelectedRepository,
@@ -140,6 +153,23 @@ IObservable<Unit> OnCloneRepository(object state)
140153
});
141154
}
142155

156+
bool IsAlreadyRepoAtPath(string path)
157+
{
158+
bool isAlreadyRepoAtPath = false;
159+
160+
if (SelectedRepository != null)
161+
{
162+
var validationResult = BaseRepositoryPathValidator.ValidationResult;
163+
if (validationResult != null && validationResult.IsValid)
164+
{
165+
string potentialPath = Path.Combine(path, SelectedRepository.Name);
166+
isAlreadyRepoAtPath = operatingSystem.Directory.Exists(potentialPath);
167+
}
168+
}
169+
170+
return isAlreadyRepoAtPath;
171+
}
172+
143173
/// <summary>
144174
/// Path to clone repositories into
145175
/// </summary>

src/GitHub.App/ViewModels/RepositoryCreationViewModel.cs

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,11 @@ public RepositoryCreationViewModel(
8080

8181
browseForDirectoryCommand.Subscribe(_ => ShowBrowseForDirectoryDialog());
8282

83-
BaseRepositoryPathValidator = this.CreateBaseRepositoryPathValidator();
83+
BaseRepositoryPathValidator = ReactivePropertyValidator.ForObservable(this.WhenAny(x => x.BaseRepositoryPath, x => x.Value))
84+
.IfNullOrEmpty("Please enter a repository path")
85+
.IfTrue(x => x.Length > 200, "Path too long")
86+
.IfContainsInvalidPathChars("Path contains invalid characters")
87+
.IfPathNotRooted("Please enter a valid path");
8488

8589
var nonNullRepositoryName = this.WhenAny(
8690
x => x.RepositoryName,
@@ -253,23 +257,11 @@ bool IsAlreadyRepoAtPath(string potentialRepositoryName)
253257
if (Path.GetInvalidPathChars().Any(chr => BaseRepositoryPath.Contains(chr)))
254258
return false;
255259
string potentialPath = Path.Combine(BaseRepositoryPath, safeRepositoryName);
256-
isAlreadyRepoAtPath = IsGitRepo(potentialPath);
260+
isAlreadyRepoAtPath = operatingSystem.Directory.Exists(potentialPath);
257261
}
258262
return isAlreadyRepoAtPath;
259263
}
260264

261-
bool IsGitRepo(string path)
262-
{
263-
try
264-
{
265-
return operatingSystem.File.Exists(Path.Combine(path, ".git", "HEAD"));
266-
}
267-
catch (PathTooLongException)
268-
{
269-
return false;
270-
}
271-
}
272-
273265
IObservable<Unit> OnCreateRepository(object state)
274266
{
275267
var newRepository = GatherRepositoryInfo();

src/GitHub.VisualStudio/UI/Views/Controls/RepositoryCreationControl.xaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@
103103
</Grid.RowDefinitions>
104104

105105
<Label Grid.Column="0" Grid.Row="0" Target="{Binding ElementName=nameText}" Content="{x:Static prop:Resources.nameText}"/>
106-
<ui:PromptTextBox x:Name="nameText" Grid.Column="1" Grid.Row="0" MaxLength="{x:Static GitHub:Constants.MaxRepositoryNameLength}"/>
106+
<ui:PromptTextBox x:Name="nameText" Grid.Column="1" Grid.Row="0" MaxLength="{x:Static GitHub:Constants.MaxRepositoryNameLength}" Text="{Binding RepositoryName, UpdateSourceTrigger=PropertyChanged}" />
107107

108108
<StackPanel Grid.Column="1" Grid.Row="1">
109109
<uirx:ValidationMessage
@@ -121,15 +121,15 @@
121121
<Label Grid.Column="0" Grid.Row="2" Target="{Binding ElementName=description}" Content="{x:Static prop:Resources.descriptionText}"/>
122122
<ui:PromptTextBox x:Name="description" Grid.Column="1" Grid.Row="2"/>
123123

124-
<Label Grid.Column="0" Grid.Row="3" Target="{Binding ElementName=localPathText}" Content="{x:Static prop:Resources.localPathText}"/>
124+
<Label Grid.Column="0" Grid.Row="3" Target="{Binding ElementName=localPathText, UpdateSourceTrigger=PropertyChanged}" Content="{x:Static prop:Resources.localPathText}"/>
125125
<Grid Grid.Column="1" Grid.Row="3">
126126

127127
<Grid.ColumnDefinitions>
128128
<ColumnDefinition Width="*" />
129129
<ColumnDefinition Width="Auto" />
130130
</Grid.ColumnDefinitions>
131131

132-
<ui:PromptTextBox x:Name="localPathText" Grid.Column="0" Grid.Row="0" />
132+
<ui:PromptTextBox x:Name="localPathText" Grid.Column="0" Grid.Row="0" Text="{Binding BaseRepositoryPath, UpdateSourceTrigger=PropertyChanged}"/>
133133
<Button
134134
x:Name="browsePathButton"
135135
Grid.Column="1"

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Threading.Tasks;
66
using GitHub.Models;
77
using GitHub.Services;
8+
using GitHub.Validation;
89
using GitHub.ViewModels;
910
using NSubstitute;
1011
using Rothko;
@@ -196,6 +197,33 @@ public void IsTrueIfLoadingReposFails()
196197
}
197198
}
198199

200+
public class TheBaseRepositoryPathValidator
201+
{
202+
[Fact]
203+
public void IsInvalidWhenDestinationRepositoryExists()
204+
{
205+
var repo = Substitute.For<IRepositoryModel>();
206+
repo.Name.Returns("bar");
207+
var repositoryHost = Substitute.For<IRepositoryHost>();
208+
repositoryHost.ModelService.GetRepositories().Returns(Observable.Return(new[] { repo }));
209+
var cloneService = Substitute.For<IRepositoryCloneService>();
210+
var os = Substitute.For<IOperatingSystem>();
211+
var directories = Substitute.For<IDirectoryFacade>();
212+
os.Directory.Returns(directories);
213+
directories.Exists(@"c:\foo\bar").Returns(true);
214+
var vm = new RepositoryCloneViewModel(
215+
repositoryHost,
216+
cloneService,
217+
os,
218+
Substitute.For<INotificationService>());
219+
220+
vm.BaseRepositoryPath = @"c:\foo";
221+
vm.SelectedRepository = repo;
222+
223+
Assert.Equal(ValidationStatus.Invalid, vm.BaseRepositoryPathValidator.ValidationResult.Status);
224+
}
225+
}
226+
199227
public class TheCloneCommand : TestBaseClass
200228
{
201229
[Fact]

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ public void IsFalseWhenRepositoryAlreadyExists(bool exists, bool expected)
281281
{
282282
var provider = Substitutes.ServiceProvider;
283283
var operatingSystem = provider.GetOperatingSystem();
284-
operatingSystem.File.Exists(@"c:\fake\foo\.git\HEAD").Returns(exists);
284+
operatingSystem.Directory.Exists(@"c:\fake\foo").Returns(exists);
285285
var vm = GetMeAViewModel(provider);
286286
vm.BaseRepositoryPath = @"c:\fake\";
287287

0 commit comments

Comments
 (0)