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

Commit 45bccfd

Browse files
authored
Merge pull request #1752 from github/fixes/1749-fetching-from-existing-remote
Create remote with the same name as upstream owner when viewing a PR
2 parents d5ddf0d + 6a78d53 commit 45bccfd

File tree

5 files changed

+120
-36
lines changed

5 files changed

+120
-36
lines changed

src/GitHub.App/Services/GitClient.cs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -96,30 +96,30 @@ public Task Fetch(IRepository repository, string remoteName)
9696

9797
public Task Fetch(IRepository repo, UriString cloneUrl, params string[] refspecs)
9898
{
99-
var httpsString = cloneUrl.ToRepositoryUrl().ToString();
100-
10199
foreach (var remote in repo.Network.Remotes)
102100
{
103-
var remoteUrl = new UriString(remote.Url);
104-
if (!remoteUrl.IsHypertextTransferProtocol)
105-
{
106-
// Only match http urls
107-
continue;
108-
}
109-
110-
var remoteHttpsString = remoteUrl.ToRepositoryUrl().ToString();
111-
if (remoteHttpsString.Equals(httpsString, StringComparison.OrdinalIgnoreCase))
101+
if (UriString.RepositoryUrlsAreEqual(new UriString(remote.Url), cloneUrl))
112102
{
113-
return Fetch(repo, defaultOriginName, refspecs);
103+
return Fetch(repo, remote.Name, refspecs);
114104
}
115105
}
116106

117107
return Task.Factory.StartNew(() =>
118108
{
119109
try
120110
{
121-
var tempRemoteName = cloneUrl.Owner + "-" + Guid.NewGuid();
122-
var remote = repo.Network.Remotes.Add(tempRemoteName, httpsString);
111+
var remoteName = cloneUrl.Owner;
112+
var remoteUri = cloneUrl.ToRepositoryUrl();
113+
114+
var removeRemote = false;
115+
if (repo.Network.Remotes[remoteName] != null)
116+
{
117+
// If a remote with this neme already exists, use a unique name and remove remote afterwards
118+
remoteName = cloneUrl.Owner + "-" + Guid.NewGuid();
119+
removeRemote = true;
120+
}
121+
122+
var remote = repo.Network.Remotes.Add(remoteName, remoteUri.ToString());
123123
try
124124
{
125125
#pragma warning disable 0618 // TODO: Replace `Network.Fetch` with `Commands.Fetch`.
@@ -128,7 +128,10 @@ public Task Fetch(IRepository repo, UriString cloneUrl, params string[] refspecs
128128
}
129129
finally
130130
{
131-
repo.Network.Remotes.Remove(tempRemoteName);
131+
if (removeRemote)
132+
{
133+
repo.Network.Remotes.Remove(remoteName);
134+
}
132135
}
133136
}
134137
catch (Exception ex)

src/GitHub.App/Services/PullRequestService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,7 @@ async Task<string> CreateRemote(IRepository repo, UriString cloneUri)
563563
{
564564
foreach (var remote in repo.Network.Remotes)
565565
{
566-
if (remote.Url == cloneUri)
566+
if (UriString.RepositoryUrlsAreEqual(new UriString(remote.Url), cloneUri))
567567
{
568568
return remote.Name;
569569
}
@@ -597,7 +597,7 @@ async Task ExtractToTempFile(
597597
string tempFilePath)
598598
{
599599
string contents;
600-
600+
601601
try
602602
{
603603
contents = await gitClient.ExtractFile(repo, commitSha, relativePath) ?? string.Empty;
@@ -680,7 +680,7 @@ static string GetSafeBranchName(string name)
680680
{
681681
var before = InvalidBranchCharsRegex.Replace(name, "-").TrimEnd('-');
682682

683-
for (;;)
683+
for (; ; )
684684
{
685685
string after = before.Replace("--", "-");
686686

src/GitHub.Exports/Primitives/UriString.cs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ public UriString(string uriString) : base(NormalizePath(uriString))
4545

4646
if (RepositoryName != null)
4747
{
48-
NameWithOwner = Owner != null
49-
? string.Format(CultureInfo.InvariantCulture, "{0}/{1}", Owner, RepositoryName)
48+
NameWithOwner = Owner != null
49+
? string.Format(CultureInfo.InvariantCulture, "{0}/{1}", Owner, RepositoryName)
5050
: RepositoryName;
5151
}
5252
}
@@ -70,12 +70,12 @@ void SetUri(Uri uri)
7070
{
7171
RepositoryName = GetRepositoryName(uri.Segments.Last());
7272
}
73-
73+
7474
if (uri.Segments.Length > 2)
7575
{
7676
Owner = (uri.Segments[uri.Segments.Length - 2] ?? "").TrimEnd('/').ToNullIfEmpty();
7777
}
78-
78+
7979
IsHypertextTransferProtocol = uri.IsHypertextTransferProtocol();
8080
}
8181

@@ -210,6 +210,24 @@ public override UriString Combine(string addition)
210210
return String.Concat(Value, addition);
211211
}
212212

213+
/// <summary>
214+
/// Compare repository URLs ignoring any trailing ".git" or difference in case.
215+
/// </summary>
216+
/// <returns>True if URLs reference the same repository.</returns>
217+
public static bool RepositoryUrlsAreEqual(UriString uri1, UriString uri2)
218+
{
219+
if (!uri1.IsHypertextTransferProtocol || !uri2.IsHypertextTransferProtocol)
220+
{
221+
// Not a repository URL
222+
return false;
223+
}
224+
225+
// Normalize repository URLs
226+
var str1 = uri1.ToRepositoryUrl().ToString();
227+
var str2 = uri2.ToRepositoryUrl().ToString();
228+
return string.Equals(str1, str2, StringComparison.OrdinalIgnoreCase);
229+
}
230+
213231
public override string ToString()
214232
{
215233
// Makes this look better in the debugger.
@@ -251,7 +269,7 @@ static string NormalizePath(string path)
251269

252270
static string GetRepositoryName(string repositoryNameSegment)
253271
{
254-
if (String.IsNullOrEmpty(repositoryNameSegment)
272+
if (String.IsNullOrEmpty(repositoryNameSegment)
255273
|| repositoryNameSegment.Equals("/", StringComparison.Ordinal))
256274
{
257275
return null;

test/UnitTests/GitHub.App/Services/GitClientTests.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,56 @@ public async Task UseExistingRemoteWhenPossible(string fetchUrl, string remoteNa
233233
}
234234
}
235235

236+
[TestCase("https://github.com/upstream_owner/repo", "origin", "https://github.com/origin_owner/repo",
237+
"upstream_owner", "https://github.com/upstream_owner/repo")]
238+
public async Task CreateRemoteWithNameOfOwner(string fetchUrl, string remoteName, string remoteUrl,
239+
string expectRemoteName, string expectRemoteUrl)
240+
{
241+
var repo = CreateRepository(remoteName, remoteUrl);
242+
var fetchUri = new UriString(fetchUrl);
243+
var refSpec = "refSpec";
244+
var gitClient = CreateGitClient();
245+
246+
await gitClient.Fetch(repo, fetchUri, refSpec);
247+
248+
repo.Network.Remotes.Received(1).Add(expectRemoteName, expectRemoteUrl);
249+
repo.Network.Remotes.Received(0).Remove(Arg.Any<string>());
250+
}
251+
252+
[TestCase("https://github.com/same_name/repo", "same_name", "https://github.com/different_name/repo",
253+
"same_name", "https://github.com/same_name/repo")]
254+
public async Task UseTemporaryRemoteWhenSameRemoteWithDifferentUrlExists(string fetchUrl, string remoteName, string remoteUrl,
255+
string expectRemoteName, string expectRemoteUrl)
256+
{
257+
var repo = CreateRepository(remoteName, remoteUrl);
258+
var fetchUri = new UriString(fetchUrl);
259+
var refSpec = "refSpec";
260+
var gitClient = CreateGitClient();
261+
262+
await gitClient.Fetch(repo, fetchUri, refSpec);
263+
264+
repo.Network.Remotes.Received(0).Add(expectRemoteName, expectRemoteUrl);
265+
repo.Network.Remotes.Received(1).Add(Arg.Any<string>(), expectRemoteUrl);
266+
repo.Network.Remotes.Received(1).Remove(Arg.Any<string>());
267+
}
268+
269+
[TestCase("https://github.com/owner/repo", "origin", "https://github.com/owner/repo", "origin")]
270+
[TestCase("https://github.com/owner/repo", "not_origin", "https://github.com/owner/repo", "not_origin")]
271+
public async Task FetchFromExistingRemote(string fetchUrl, string remoteName, string remoteUrl, string expectRemoteName)
272+
{
273+
var repo = CreateRepository(remoteName, remoteUrl);
274+
var fetchUri = new UriString(fetchUrl);
275+
var refSpec = "refSpec";
276+
var gitClient = CreateGitClient();
277+
278+
await gitClient.Fetch(repo, fetchUri, refSpec);
279+
280+
var remote = repo.Network.Remotes[expectRemoteName];
281+
#pragma warning disable 0618 // TODO: Replace `Network.Fetch` with `Commands.Fetch`.
282+
repo.Network.Received(1).Fetch(remote, Arg.Any<string[]>(), Arg.Any<FetchOptions>());
283+
#pragma warning restore 0618
284+
}
285+
236286
static IRepository CreateRepository(string remoteName, string remoteUrl)
237287
{
238288
var remote = Substitute.For<Remote>();

test/UnitTests/GitHub.Primitives/UriStringTests.cs

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ public class UriStringTests
77
{
88
public class TheConstructor : TestBaseClass
99
{
10-
1110
[TestCase("http://192.168.1.3/foo/bar.git", "192.168.1.3", "foo", "bar")]
1211
[TestCase("http://haacked@example.com/foo/bar", "example.com", "foo", "bar")]
1312
[TestCase("http://haacked:password@example.com/foo/bar", "example.com", "foo", "bar")]
@@ -41,7 +40,6 @@ public void ParsesWellFormedUrlComponents(string url, string expectedHost, strin
4140
Assert.False(cloneUrl.IsFileUri);
4241
}
4342

44-
4543
[TestCase(@"..\bar\foo")]
4644
[TestCase(@"..\..\foo")]
4745
[TestCase(@"../..\foo")]
@@ -67,7 +65,6 @@ public void ParsesLocalFileUris(string path)
6765
Assert.True(cloneUrl.IsFileUri);
6866
}
6967

70-
7168
[TestCase("complete garbage", "", "", null)]
7269
[TestCase(@"..\other_folder", "", "", "other_folder")]
7370
[TestCase("http://example.com", "example.com", null, null)]
@@ -89,7 +86,6 @@ public void ParsesWeirdUrlsAsWellAsPossible(string url, string expectedHost, str
8986
Assert.That(cloneUrl.RepositoryName, Is.EqualTo(repositoryName));
9087
}
9188

92-
9389
[TestCase(@"http:\\example.com/bar\baz")]
9490
[TestCase(@"http://example.com/bar/baz")]
9591
public void NormalizesSeparator(string uriString)
@@ -107,7 +103,6 @@ public void AcceptsNullConversion()
107103

108104
public class TheNameWithOwnerProperty : TestBaseClass
109105
{
110-
111106
[TestCase("http://192.168.1.3/foo/bar.git", "foo/bar")]
112107
[TestCase("http://192.168.1.3/foo/bar", "foo/bar")]
113108
[TestCase("http://192.168.1.3/foo/bar/baz/qux", "baz/qux")]
@@ -125,7 +120,6 @@ public void DependsOnOwnerAndRepoNameNotBeingNull(string url, string expectedNam
125120

126121
public class TheCombineMethod : TestBaseClass
127122
{
128-
129123
[TestCase("http://example.com", "foo/bar", @"http://example.com/foo/bar")]
130124
[TestCase("http://example.com/", "foo/bar", @"http://example.com/foo/bar")]
131125
[TestCase("http://example.com/", "/foo/bar/", @"http://example.com/foo/bar/")]
@@ -146,7 +140,6 @@ public void ComparesHostInsensitively(string uriString, string path, string expe
146140

147141
public class TheIsValidUriProperty : TestBaseClass
148142
{
149-
150143
[TestCase("http://example.com/", true)]
151144
[TestCase("file:///C:/dev/exp/foo", true)]
152145
[TestCase("garbage", false)]
@@ -159,7 +152,6 @@ public void ReturnWhetherTheUriIsParseableByUri(string uriString, bool expected)
159152

160153
public class TheToRepositoryUrlMethod : TestBaseClass
161154
{
162-
163155
[TestCase("file:///C:/dev/exp/foo", "file:///C:/dev/exp/foo")]
164156
[TestCase("http://example.com/", "http://example.com/")]
165157
[TestCase("http://haacked@example.com/foo/bar", "http://example.com/foo/bar")]
@@ -176,7 +168,6 @@ public void ConvertsToWebUrl(string uriString, string expected)
176168
Assert.That(new UriString(uriString).ToRepositoryUrl(), Is.EqualTo(new Uri(expected)));
177169
}
178170

179-
180171
[TestCase("file:///C:/dev/exp/foo", "file:///C:/dev/exp/foo")]
181172
[TestCase("http://example.com/", "http://example.com/")]
182173
[TestCase("http://haacked@example.com/foo/bar", "http://example.com/baz/bar")]
@@ -208,7 +199,7 @@ public void ConvertsWithNewOwner(string uriString, string expected, string owner
208199
[TestCase("git@example.com:org/repo.git", "https://example.com/org/repo")]
209200
[TestCase("ssh://git@github.com:443/shana/cef", "https://github.com/shana/cef")]
210201
[TestCase("ssh://git@example.com:23/haacked/encourage", "https://example.com:23/haacked/encourage")]
211-
202+
212203
[TestCase("asdf", null)]
213204
[TestCase("", null)]
214205
[TestCase("file:///C:/dev/exp/foo", "file:///C:/dev/exp/foo")]
@@ -233,7 +224,6 @@ public void ShouldNeverThrow(string url, string expected)
233224

234225
public class TheAdditionOperator : TestBaseClass
235226
{
236-
237227
[TestCase("http://example.com", "foo/bar", @"http://example.com/foo/bar")]
238228
[TestCase("http://example.com/", "foo/bar", @"http://example.com/foo/bar")]
239229
[TestCase("http://example.com/", "/foo/bar/", @"http://example.com/foo/bar/")]
@@ -293,7 +283,6 @@ public void ConvertsNullToNull()
293283

294284
public class TheIsHypertextTransferProtocolProperty : TestBaseClass
295285
{
296-
297286
[TestCase("http://example.com", true)]
298287
[TestCase("HTTP://example.com", true)]
299288
[TestCase("https://example.com", true)]
@@ -310,7 +299,6 @@ public void IsTrueOnlyForHttpAndHttps(string url, bool expected)
310299

311300
public class TheEqualsMethod : TestBaseClass
312301
{
313-
314302
[TestCase("https://github.com/foo/bar", "https://github.com/foo/bar", true)]
315303
[TestCase("https://github.com/foo/bar", "https://github.com/foo/BAR", false)]
316304
[TestCase("https://github.com/foo/bar", "https://github.com/foo/bar/", false)]
@@ -334,4 +322,29 @@ public void MakesUriStringSuitableForDictionaryKey()
334322
Assert.True(dictionary.ContainsKey(new UriString("https://github.com/foo/bar")));
335323
}
336324
}
325+
326+
public class TheRepositoryUrlsAreEqualMethod
327+
{
328+
[TestCase("https://github.com/owner/repo", "https://github.com/owner/repo", true)]
329+
[TestCase("https://github.com/owner/repo", "HTTPS://github.com/OWNER/REPO", true)]
330+
[TestCase("https://github.com/owner/repo.git", "https://github.com/owner/repo", true)]
331+
[TestCase("https://github.com/owner/repo", "https://github.com/owner/repo.git", true)]
332+
[TestCase("https://github.com/owner/repo", "https://github.com/different_owner/repo", false)]
333+
[TestCase("https://github.com/owner/repo", "https://github.com/owner/different_repo", false)]
334+
[TestCase("ssh://git@github.com:443/shana/cef", "https://github.com/shana/cef", false)]
335+
[TestCase("file://github.com/github/visualstudio", "https://github.com/github/visualstudio", false)]
336+
[TestCase("http://github.com/github/visualstudio", "https://github.com/github/visualstudio", false,
337+
Description = "http is different to https")]
338+
[TestCase("ssh://git@github.com:443/shana/cef", "ssh://git@github.com:443/shana/cef", false,
339+
Description = "The same but not a repository URL")]
340+
public void RepositoryUrlsAreEqual(string url1, string url2, bool expectEqual)
341+
{
342+
var uriString1 = new UriString(url1);
343+
var uriString2 = new UriString(url2);
344+
345+
var equal = UriString.RepositoryUrlsAreEqual(uriString1, uriString2);
346+
347+
Assert.That(equal, Is.EqualTo(expectEqual));
348+
}
349+
}
337350
}

0 commit comments

Comments
 (0)