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

Commit d0ae1d5

Browse files
authored
Merge pull request #1906 from github/refactor/clone-include-owner-in-path
Include owner in default path when cloning a repository
2 parents 17d756b + 5b2e8ec commit d0ae1d5

File tree

2 files changed

+116
-30
lines changed

2 files changed

+116
-30
lines changed

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

+51-10
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class RepositoryCloneViewModel : ViewModelBase, IRepositoryCloneViewModel
2525
readonly IRepositoryCloneService service;
2626
readonly IReadOnlyList<IRepositoryCloneTabViewModel> tabs;
2727
string path;
28+
IRepositoryModel previousRepository;
2829
ObservableAsPropertyHelper<string> pathError;
2930
int selectedTabIndex;
3031

@@ -54,7 +55,7 @@ public RepositoryCloneViewModel(
5455

5556
pathError = Observable.CombineLatest(
5657
repository,
57-
this.WhenAnyValue(x => x.Path),
58+
this.WhenAnyValue(x => x.Path),
5859
ValidatePath)
5960
.ToProperty(this, x => x.PathError);
6061

@@ -138,19 +139,59 @@ void BrowseForDirectory()
138139
}
139140
}
140141

141-
void UpdatePath(IRepositoryModel x)
142+
void UpdatePath(IRepositoryModel repository)
142143
{
143-
if (x != null)
144+
if (repository != null)
144145
{
145-
if (Path == service.DefaultClonePath)
146-
{
147-
Path = System.IO.Path.Combine(Path, x.Name);
148-
}
149-
else
146+
var basePath = GetUpdatedBasePath(Path);
147+
previousRepository = repository;
148+
Path = System.IO.Path.Combine(basePath, repository.Owner, repository.Name);
149+
}
150+
}
151+
152+
string GetUpdatedBasePath(string path)
153+
{
154+
if (string.IsNullOrEmpty(path))
155+
{
156+
return service.DefaultClonePath;
157+
}
158+
159+
if (previousRepository == null)
160+
{
161+
return path;
162+
}
163+
164+
if (FindDirWithout(path, previousRepository?.Owner, 2) is string dirWithoutOwner)
165+
{
166+
return dirWithoutOwner;
167+
}
168+
169+
if (FindDirWithout(path, previousRepository?.Name, 1) is string dirWithoutRepo)
170+
{
171+
return dirWithoutRepo;
172+
}
173+
174+
return path;
175+
176+
string FindDirWithout(string dir, string match, int levels)
177+
{
178+
string dirWithout = null;
179+
for (var i = 0; i < 2; i++)
150180
{
151-
var basePath = System.IO.Path.GetDirectoryName(Path);
152-
Path = System.IO.Path.Combine(basePath, x.Name);
181+
if (string.IsNullOrEmpty(dir))
182+
{
183+
break;
184+
}
185+
186+
var name = System.IO.Path.GetFileName(dir);
187+
dir = System.IO.Path.GetDirectoryName(dir);
188+
if (name == match)
189+
{
190+
dirWithout = dir;
191+
}
153192
}
193+
194+
return dirWithout;
154195
}
155196
}
156197

test/GitHub.App.UnitTests/ViewModels/Dialog/Clone/RepositoryCloneViewModelTests.cs

+65-20
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,13 @@ public async Task Path_Is_Initialized()
111111
}
112112

113113
[Test]
114-
public async Task Repository_Name_Is_Appended_To_Base_Path()
114+
public async Task Owner_And_Repository_Name_Is_Appended_To_Base_Path()
115115
{
116116
var target = CreateTarget();
117-
var repository = Substitute.For<IRepositoryModel>();
118117

119-
repository.Name.Returns("repo");
120-
SetRepository(target.GitHubTab, repository);
118+
SetRepository(target.GitHubTab, CreateRepositoryModel("owner", "repo"));
121119

122-
Assert.That(target.Path, Is.EqualTo("d:\\efault\\path\\repo"));
120+
Assert.That(target.Path, Is.EqualTo("d:\\efault\\path\\owner\\repo"));
123121
}
124122

125123
[Test]
@@ -136,10 +134,7 @@ public async Task PathError_Is_Not_Set_When_No_Repository_Selected()
136134
public async Task PathError_Is_Set_For_Existing_Destination()
137135
{
138136
var target = CreateTarget();
139-
var repository = Substitute.For<IRepositoryModel>();
140-
141-
repository.Name.Returns("repo");
142-
SetRepository(target.GitHubTab, repository);
137+
SetRepository(target.GitHubTab, CreateRepositoryModel("owner", "repo"));
143138
target.Path = "d:\\exists";
144139

145140
Assert.That(target.PathError, Is.EqualTo(Resources.DestinationAlreadyExists));
@@ -149,13 +144,47 @@ public async Task PathError_Is_Set_For_Existing_Destination()
149144
public async Task Repository_Name_Replaces_Last_Part_Of_Non_Base_Path()
150145
{
151146
var target = CreateTarget();
152-
var repository = Substitute.For<IRepositoryModel>();
153147

154-
target.Path = "d:\\efault\\foo";
155-
repository.Name.Returns("repo");
156-
SetRepository(target.GitHubTab, repository);
148+
var owner = "owner";
149+
target.Path = "d:\\efault";
150+
SetRepository(target.GitHubTab, CreateRepositoryModel(owner, "name"));
151+
target.Path = $"d:\\efault\\{owner}\\foo";
152+
SetRepository(target.GitHubTab, CreateRepositoryModel(owner, "repo"));
153+
154+
Assert.That(target.Path, Is.EqualTo($"d:\\efault\\{owner}\\repo"));
155+
}
156+
157+
[TestCase("c:\\base", "owner1/repo1", "c:\\base\\owner1\\repo1", "owner2/repo2", "c:\\base\\owner2\\repo2",
158+
Description = "Path unchanged")]
159+
[TestCase("c:\\base", "owner1/repo1", "c:\\base\\owner1\\changed", "owner2/repo2", "c:\\base\\owner2\\repo2",
160+
Description = "Repo name changed")]
161+
[TestCase("c:\\base", "owner1/repo1", "c:\\base\\owner1", "owner2/repo2", "c:\\base\\owner2\\repo2",
162+
Description = "Repo name deleted")]
163+
[TestCase("c:\\base", "owner1/repo1", "c:\\base", "owner2/repo2", "c:\\base\\owner2\\repo2",
164+
Description = "Base path reverted")]
165+
166+
[TestCase("c:\\base", "owner1/repo1", "c:\\new\\base\\owner1\\changed", "owner2/repo2", "c:\\new\\base\\owner2\\repo2",
167+
Description = "Base path and repo name changed")]
168+
[TestCase("c:\\base", "owner1/repo1", "c:\\new\\base\\owner1", "owner2/repo2", "c:\\new\\base\\owner2\\repo2",
169+
Description = "Base path changed and repo name deleted")]
170+
[TestCase("c:\\base", "owner1/repo1", "c:\\new\\base", "owner2/repo2", "c:\\new\\base\\owner2\\repo2",
171+
Description = "Base path changed and repo owner/name deleted")]
172+
173+
[TestCase("c:\\base", "owner1/repo1", "", "owner2/repo2", "c:\\base\\owner2\\repo2",
174+
Description = "Base path cleared")]
175+
[TestCase("c:\\base", "owner1/repo1", "c:\\base\\repo1", "owner2/repo2", "c:\\base\\owner2\\repo2",
176+
Description = "Owner deleted")]
177+
[TestCase("c:\\base", "same/same", "c:\\base\\same\\same", "owner2/repo2", "c:\\base\\owner2\\repo2",
178+
Description = "Owner and repo have same name")]
179+
public async Task User_Edits_Path(string defaultClonePath, string repo1, string userPath, string repo2, string expectPath)
180+
{
181+
var target = CreateTarget(defaultClonePath: defaultClonePath);
182+
SetRepository(target.GitHubTab, CreateRepositoryModel(repo1));
183+
target.Path = userPath;
184+
185+
SetRepository(target.GitHubTab, CreateRepositoryModel(repo2));
157186

158-
Assert.That(target.Path, Is.EqualTo("d:\\efault\\repo"));
187+
Assert.That(target.Path, Is.EqualTo(expectPath));
159188
}
160189

161190
[Test]
@@ -175,7 +204,7 @@ public async Task Clone_Is_Enabled_When_Repository_Selected()
175204

176205
await target.InitializeAsync(null);
177206

178-
SetRepository(target.GitHubTab, Substitute.For<IRepositoryModel>());
207+
SetRepository(target.GitHubTab, CreateRepositoryModel());
179208

180209
Assert.That(target.Clone.CanExecute(null), Is.True);
181210
}
@@ -187,7 +216,7 @@ public async Task Clone_Is_Disabled_When_Has_PathError()
187216

188217
await target.InitializeAsync(null);
189218

190-
SetRepository(target.GitHubTab, Substitute.For<IRepositoryModel>());
219+
SetRepository(target.GitHubTab, CreateRepositoryModel());
191220
Assert.That(target.Clone.CanExecute(null), Is.True);
192221

193222
target.Path = "d:\\exists";
@@ -233,10 +262,10 @@ static IRepositorySelectViewModel CreateSelectViewModel()
233262
return result;
234263
}
235264

236-
static IRepositoryCloneService CreateRepositoryCloneService()
265+
static IRepositoryCloneService CreateRepositoryCloneService(string defaultClonePath)
237266
{
238267
var result = Substitute.For<IRepositoryCloneService>();
239-
result.DefaultClonePath.Returns("d:\\efault\\path");
268+
result.DefaultClonePath.Returns(defaultClonePath);
240269
result.DestinationExists("d:\\exists").Returns(true);
241270
return result;
242271
}
@@ -247,11 +276,12 @@ static RepositoryCloneViewModel CreateTarget(
247276
IRepositoryCloneService service = null,
248277
IRepositorySelectViewModel gitHubTab = null,
249278
IRepositorySelectViewModel enterpriseTab = null,
250-
IRepositoryUrlViewModel urlTab = null)
279+
IRepositoryUrlViewModel urlTab = null,
280+
string defaultClonePath = "d:\\efault\\path")
251281
{
252282
os = os ?? Substitute.For<IOperatingSystem>();
253283
connectionManager = connectionManager ?? CreateConnectionManager("https://github.com");
254-
service = service ?? CreateRepositoryCloneService();
284+
service = service ?? CreateRepositoryCloneService(defaultClonePath);
255285
gitHubTab = gitHubTab ?? CreateSelectViewModel();
256286
enterpriseTab = enterpriseTab ?? CreateSelectViewModel();
257287
urlTab = urlTab ?? Substitute.For<IRepositoryUrlViewModel>();
@@ -264,5 +294,20 @@ static RepositoryCloneViewModel CreateTarget(
264294
enterpriseTab,
265295
urlTab);
266296
}
297+
298+
static IRepositoryModel CreateRepositoryModel(string repo = "owner/repo")
299+
{
300+
var split = repo.Split('/');
301+
var (owner, name) = (split[0], split[1]);
302+
return CreateRepositoryModel(owner, name);
303+
}
304+
305+
static IRepositoryModel CreateRepositoryModel(string owner, string name)
306+
{
307+
var repository = Substitute.For<IRepositoryModel>();
308+
repository.Owner.Returns(owner);
309+
repository.Name.Returns(name);
310+
return repository;
311+
}
267312
}
268313
}

0 commit comments

Comments
 (0)