Skip to content
This repository was archived by the owner on Jan 11, 2024. It is now read-only.

Commit 91726ae

Browse files
authored
Add minimal VSTS VersionTools auto-PR client (#2135)
1 parent 4642e67 commit 91726ae

12 files changed

Lines changed: 727 additions & 90 deletions

File tree

src/Microsoft.DotNet.Build.Tasks/Microsoft.DotNet.Build.Tasks.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
<Compile Include="VersionTools\BaseDependenciesTask.cs" />
7676
<Compile Include="VersionTools\MsBuildTraceListener.cs" />
7777
<Compile Include="ReadGitConfigFile.cs" />
78+
<Compile Include="VersionTools\PullRequestServiceType.cs" />
7879
<Compile Include="VersionTools\SubmitPullRequest.cs" />
7980
<Compile Include="VersionTools\UpdateToRemoteDependencies.cs" />
8081
<Compile Include="VersionTools\TraceListenerCollectionExtensions.cs" />

src/Microsoft.DotNet.Build.Tasks/PackageFiles/VersionTools.targets

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,16 +155,29 @@
155155
<Target Name="SubmitPullRequestIfChanged"
156156
Condition="'$(MadeChanges)' == 'true'">
157157
<PropertyGroup>
158+
<PullRequestServiceType Condition="'$(PullRequestServiceType)' == ''">GitHub</PullRequestServiceType>
158159
<MaintainersCanModifyPullRequest Condition="'$(MaintainersCanModifyPullRequest)' == ''">true</MaintainersCanModifyPullRequest>
159160
<TrackDiscardedCommits Condition="'$(TrackDiscardedCommits)' == ''">true</TrackDiscardedCommits>
160161
</PropertyGroup>
161-
<SubmitPullRequest ProjectRepoName="$(ProjectRepoName)"
162+
163+
<!-- If GitHub service, set up backward-compatible properties. -->
164+
<PropertyGroup Condition="'$(PullRequestServiceType)' == 'GitHub'">
165+
<PullRequestAuthToken Condition="'$(PullRequestAuthToken)' == ''">$(GitHubAuthToken)</PullRequestAuthToken>
166+
<PullRequestUser Condition="'$(PullRequestUser)' == ''">$(GitHubUser)</PullRequestUser>
167+
<PullRequestAuthor Condition="'$(PullRequestAuthor)' == ''">$(GitHubAuthor)</PullRequestAuthor>
168+
<PullRequestEmail Condition="'$(PullRequestEmail)' == ''">$(GitHubEmail)</PullRequestEmail>
169+
</PropertyGroup>
170+
171+
<SubmitPullRequest PullRequestServiceType="$(PullRequestServiceType)"
172+
PullRequestAuthToken="$(PullRequestAuthToken)"
173+
PullRequestUser="$(PullRequestUser)"
174+
PullRequestAuthor="$(PullRequestAuthor)"
175+
PullRequestEmail="$(PullRequestEmail)"
176+
VstsInstanceName="$(VstsInstanceName)"
177+
VstsApiVersionOverride="$(VstsApiVersionOverride)"
162178
ProjectRepoOwner="$(ProjectRepoOwner)"
179+
ProjectRepoName="$(ProjectRepoName)"
163180
ProjectRepoBranch="$(ProjectRepoBranch)"
164-
GitHubAuthToken="$(GitHubAuthToken)"
165-
GitHubUser="$(GitHubUser)"
166-
GitHubEmail="$(GitHubEmail)"
167-
GitHubAuthor="$(GitHubAuthor)"
168181
CommitMessage="$(CommitMessage)"
169182
Title="$(PullRequestTitle)"
170183
Body="$(PullRequestBody)"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace Microsoft.DotNet.Build.Tasks.VersionTools
6+
{
7+
public enum PullRequestServiceType
8+
{
9+
GitHub,
10+
Vsts
11+
}
12+
}

src/Microsoft.DotNet.Build.Tasks/VersionTools/SubmitPullRequest.cs

Lines changed: 93 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using Microsoft.Build.Framework;
66
using Microsoft.DotNet.VersionTools.Automation;
77
using Microsoft.DotNet.VersionTools.Automation.GitHubApi;
8+
using Microsoft.DotNet.VersionTools.Automation.VstsApi;
9+
using System;
810
using System.Diagnostics;
911
using System.Linq;
1012

@@ -13,12 +15,47 @@ namespace Microsoft.DotNet.Build.Tasks.VersionTools
1315
public class SubmitPullRequest : BuildTask
1416
{
1517
[Required]
16-
public string GitHubAuthToken { get; set; }
18+
public string PullRequestServiceType { get; set; }
19+
20+
[Required]
21+
public string PullRequestAuthToken { get; set; }
22+
23+
/// <summary>
24+
/// The name of the user creating this PR. Used as the default for PullRequestAuthor.
25+
///
26+
/// For GitHub, this locates the dev (origin) fork.
27+
///
28+
/// For VSTS, this is only used as a default for PullRequestAuthor. (PullRequestAuthToken is
29+
/// used to fetch the calling user's GUID from VSTS. The GUID is used to search for existing
30+
/// PRs, like GitHub username is used to find GitHub PRs. However, VSTS user GUID isn't a
31+
/// good commit author, so the caller must provide a friendly name.)
32+
/// </summary>
1733
[Required]
18-
public string GitHubUser { get; set; }
34+
public string PullRequestUser { get; set; }
35+
36+
/// <summary>
37+
/// Sets the Git author for the update commit. Defaults to PullRequestUser's value.
38+
/// </summary>
39+
public string PullRequestAuthor { get; set; }
40+
41+
/// <summary>
42+
/// Sets the Git author's email for the update commit.
43+
/// </summary>
1944
[Required]
20-
public string GitHubEmail { get; set; }
45+
public string PullRequestEmail { get; set; }
46+
47+
/// <summary>
48+
/// Required for VSTS PullRequestServiceType. Used to find the VSTS repository.
49+
/// </summary>
50+
public string VstsInstanceName { get; set; }
2151

52+
public string VstsApiVersionOverride { get; set; }
53+
54+
/// <summary>
55+
/// For GitHub, the upstream repository owner. Defaults to 'dotnet'.
56+
///
57+
/// For VSTS, the project containing the repo.
58+
/// </summary>
2259
public string ProjectRepoOwner { get; set; }
2360

2461
[Required]
@@ -34,17 +71,12 @@ public class SubmitPullRequest : BuildTask
3471
public string Title { get; set; }
3572

3673
/// <summary>
37-
/// Body of the pull request. Optional.
74+
/// Body/description of the pull request. Optional.
3875
///
39-
/// Only used when submitting a new pull request or if TrackDiscardedCommits is false.
76+
/// This will overwrite the current PR body if updating a PR.
4077
/// </summary>
4178
public string Body { get; set; }
4279

43-
/// <summary>
44-
/// The git author of the update commit. Defaults to the same as GitHubUser.
45-
/// </summary>
46-
public string GitHubAuthor { get; set; }
47-
4880
public ITaskItem[] NotifyGitHubUsers { get; set; }
4981

5082
public bool AlwaysCreateNewPullRequest { get; set; }
@@ -61,12 +93,11 @@ public override bool Execute()
6193

6294
private void TraceListenedExecute()
6395
{
64-
var auth = new GitHubAuth(GitHubAuthToken, GitHubUser, GitHubEmail);
96+
// GitHub and VSTS have different dev flow conventions.
97+
GitHubProject origin;
6598

66-
using (GitHubClient client = new GitHubClient(auth))
99+
using (IGitHubClient client = CreateClient(out origin))
67100
{
68-
var origin = new GitHubProject(ProjectRepoName, GitHubUser);
69-
70101
var upstreamBranch = new GitHubBranch(
71102
ProjectRepoBranch,
72103
new GitHubProject(ProjectRepoName, ProjectRepoOwner));
@@ -78,19 +109,61 @@ private void TraceListenedExecute()
78109
body += PullRequestCreator.NotificationString(NotifyGitHubUsers.Select(item => item.ItemSpec));
79110
}
80111

81-
var prCreator = new PullRequestCreator(client.Auth, GitHubAuthor);
112+
var options = new PullRequestOptions
113+
{
114+
ForceCreate = AlwaysCreateNewPullRequest,
115+
MaintainersCanModify = MaintainersCanModifyPullRequest,
116+
TrackDiscardedCommits = TrackDiscardedCommits
117+
};
118+
119+
var prCreator = new PullRequestCreator(client.Auth, PullRequestAuthor);
82120
prCreator.CreateOrUpdateAsync(
83121
CommitMessage,
84122
CommitMessage + $" ({ProjectRepoBranch})",
85123
body,
86124
upstreamBranch,
87125
origin,
88-
new PullRequestOptions
126+
options,
127+
client).Wait();
128+
}
129+
}
130+
131+
private IGitHubClient CreateClient(out GitHubProject origin)
132+
{
133+
PullRequestServiceType type;
134+
if (!Enum.TryParse(PullRequestServiceType, true, out type))
135+
{
136+
string options = string.Join(", ", Enum.GetNames(typeof(PullRequestServiceType)));
137+
throw new ArgumentException(
138+
$"{nameof(PullRequestServiceType)} '{PullRequestServiceType}' is not valid. " +
139+
$"Options are {options}");
140+
}
141+
142+
var auth = new GitHubAuth(
143+
PullRequestAuthToken,
144+
PullRequestUser,
145+
PullRequestEmail);
146+
147+
switch (type)
148+
{
149+
case VersionTools.PullRequestServiceType.GitHub:
150+
origin = new GitHubProject(ProjectRepoName, PullRequestUser);
151+
return new GitHubClient(auth);
152+
153+
case VersionTools.PullRequestServiceType.Vsts:
154+
if (string.IsNullOrEmpty(VstsInstanceName))
89155
{
90-
ForceCreate = AlwaysCreateNewPullRequest,
91-
MaintainersCanModify = MaintainersCanModifyPullRequest,
92-
TrackDiscardedCommits = TrackDiscardedCommits
93-
}).Wait();
156+
throw new ArgumentException($"{nameof(VstsInstanceName)} is required but not set.");
157+
}
158+
159+
origin = new GitHubProject(ProjectRepoName, ProjectRepoOwner);
160+
return new VstsAdapterClient(auth, VstsInstanceName, VstsApiVersionOverride);
161+
162+
default:
163+
throw new ArgumentOutOfRangeException(
164+
nameof(PullRequestServiceType),
165+
type,
166+
"Enum value invalid.");
94167
}
95168
}
96169
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Text;
7+
8+
namespace Microsoft.DotNet.VersionTools.Automation
9+
{
10+
internal class ClientHelpers
11+
{
12+
public static string ToBase64(string value) => Convert.ToBase64String(Encoding.UTF8.GetBytes(value));
13+
public static string FromBase64(string value) => Encoding.UTF8.GetString(Convert.FromBase64String(value));
14+
}
15+
}

src/Microsoft.DotNet.VersionTools/Automation/GitHubApi/GitHubClient.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
using System.Linq;
1212
using System.Net;
1313
using System.Net.Http;
14-
using System.Text;
1514
using System.Threading.Tasks;
1615

1716
namespace Microsoft.DotNet.VersionTools.Automation.GitHubApi
@@ -79,7 +78,7 @@ public async Task<string> GetGitHubFileContentsAsync(
7978
try
8079
{
8180
GitHubContents file = await GetGitHubFileAsync(path, branch.Project, $"heads/{branch.Name}");
82-
return FromBase64(file.Content);
81+
return ClientHelpers.FromBase64(file.Content);
8382
}
8483
catch (HttpFailureResponseException ex) when (ex.HttpStatusCode == HttpStatusCode.NotFound)
8584
{
@@ -95,7 +94,7 @@ public async Task<string> GetGitHubFileContentsAsync(
9594
try
9695
{
9796
GitHubContents file = await GetGitHubFileAsync(path, project, @ref);
98-
return FromBase64(file.Content);
97+
return ClientHelpers.FromBase64(file.Content);
9998
}
10099
catch (HttpFailureResponseException ex) when (ex.HttpStatusCode == HttpStatusCode.NotFound)
101100
{
@@ -128,7 +127,7 @@ public async Task PutGitHubFileAsync(
128127
name = Auth.User,
129128
email = Auth.Email
130129
},
131-
content = ToBase64(newFileContents),
130+
content = ClientHelpers.ToBase64(newFileContents),
132131
sha = currentSha
133132
}, Formatting.Indented);
134133

@@ -386,6 +385,13 @@ public async Task<GitReference> PatchReferenceAsync(GitHubProject project, strin
386385
}
387386
}
388387

388+
public Task<string> GetMyAuthorIdAsync() => Task.FromResult(Auth.User);
389+
390+
public string CreateGitRemoteUrl(GitHubProject project) => $"github.com/{project.Segments}.git";
391+
392+
public void AdjustOptionsToCapability(PullRequestOptions options)
393+
{ }
394+
389395
private void EnsureAuthenticated()
390396
{
391397
if (Auth == null)
@@ -436,8 +442,5 @@ private static async Task<string> GetPullRequestUrlAsync(HttpResponseMessage res
436442
JObject responseContent = JObject.Parse(await response.Content.ReadAsStringAsync());
437443
return responseContent["html_url"].ToString();
438444
}
439-
440-
private static string ToBase64(string value) => Convert.ToBase64String(Encoding.UTF8.GetBytes(value));
441-
private static string FromBase64(string value) => Encoding.UTF8.GetString(Convert.FromBase64String(value));
442445
}
443446
}

src/Microsoft.DotNet.VersionTools/Automation/GitHubApi/IGitHubClient.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System;
56
using System.Threading.Tasks;
67

78
namespace Microsoft.DotNet.VersionTools.Automation.GitHubApi
89
{
9-
public interface IGitHubClient
10+
public interface IGitHubClient : IDisposable
1011
{
1112
GitHubAuth Auth { get; }
1213

@@ -83,5 +84,17 @@ Task<GitReference> PatchReferenceAsync(
8384
string @ref,
8485
string sha,
8586
bool force);
87+
88+
/// <summary>
89+
/// Get author ID in a form that can be used to search for pull requests. For GitHub, this
90+
/// is simply the auth username. For VSTS, this is a GUID fetched from an API.
91+
/// </summary>
92+
Task<string> GetMyAuthorIdAsync();
93+
94+
string CreateGitRemoteUrl(
95+
GitHubProject project);
96+
97+
void AdjustOptionsToCapability(
98+
PullRequestOptions options);
8699
}
87100
}

0 commit comments

Comments
 (0)