@@ -10,6 +10,7 @@ import 'package:process/process.dart';
1010import 'git.dart' ;
1111import 'globals.dart' ;
1212import 'repository.dart' ;
13+ import 'stdio.dart' ;
1314
1415/// A service for rolling the SDK's pub packages to latest and open a PR upstream.
1516class PackageAutoroller {
@@ -19,7 +20,10 @@ class PackageAutoroller {
1920 required this .framework,
2021 required this .orgName,
2122 required this .processManager,
23+ this .githubUsername = 'fluttergithubbot' ,
24+ Stdio ? stdio,
2225 }) {
26+ this .stdio = stdio ?? VerboseStdio .local ();
2327 if (token.trim ().isEmpty) {
2428 throw Exception ('empty token!' );
2529 }
@@ -31,12 +35,16 @@ class PackageAutoroller {
3135 }
3236 }
3337
38+ late final Stdio stdio;
39+
3440 final FrameworkRepository framework;
3541 final ProcessManager processManager;
3642
3743 /// Path to GitHub CLI client.
3844 final String githubClient;
3945
46+ final String githubUsername;
47+
4048 /// GitHub API access token.
4149 final String token;
4250
@@ -63,23 +71,46 @@ This PR was generated by `flutter update-packages --force-upgrade`.
6371 return name (x);
6472 })();
6573
74+ void log (String message) {
75+ stdio.printStatus (_redactToken (message));
76+ }
77+
6678 /// Name of the GitHub organization to push the feature branch to.
6779 final String orgName;
6880
6981 Future <void > roll () async {
70- await authLogin ();
71- await updatePackages ();
72- await pushBranch ();
73- await createPr (
74- repository: await framework.checkoutDirectory,
75- );
76- await authLogout ();
82+ try {
83+ await authLogin ();
84+ final bool openPrAlready = await hasOpenPrs ();
85+ if (openPrAlready) {
86+ // Don't open multiple roll PRs.
87+ return ;
88+ }
89+ final bool didUpdate = await updatePackages ();
90+ if (! didUpdate) {
91+ log ('Packages are already at latest.' );
92+ return ;
93+ }
94+ await pushBranch ();
95+ await createPr (repository: await framework.checkoutDirectory);
96+ await authLogout ();
97+ } on Exception catch (exception) {
98+ final String message = _redactToken (exception.toString ());
99+ throw Exception ('${exception .runtimeType }: $message ' );
100+ }
77101 }
78102
79- Future <void > updatePackages ({
103+ // Ensure we don't leak the GitHub token in exception messages
104+ String _redactToken (String message) => message.replaceAll (token, '[GitHub TOKEN]' );
105+
106+ /// Attempt to update all pub packages.
107+ ///
108+ /// Will return whether or not any changes were made.
109+ Future <bool > updatePackages ({
80110 bool verbose = true ,
81- String author = 'flutter-packages-autoroller <flutter-packages-autoroller@google.com>'
82111 }) async {
112+ final String author = '$githubUsername <$githubUsername @gmail.com>' ;
113+
83114 await framework.newBranch (await featureBranchName);
84115 final io.Process flutterProcess = await framework.streamFlutter (< String > [
85116 if (verbose) '--verbose' ,
@@ -90,18 +121,26 @@ This PR was generated by `flutter update-packages --force-upgrade`.
90121 if (exitCode != 0 ) {
91122 throw ConductorException ('Failed to update packages with exit code $exitCode ' );
92123 }
124+ // If the git checkout is clean, then pub packages are already at latest that cleanly resolve.
125+ if (await framework.gitCheckoutClean ()) {
126+ return false ;
127+ }
93128 await framework.commit (
94129 'roll packages' ,
95130 addFirst: true ,
96131 author: author,
97132 );
133+ return true ;
98134 }
99135
100136 Future <void > pushBranch () async {
137+ final String projectName = framework.mirrorRemote! .url.split (r'/' ).last;
138+ // Encode the token into the remote URL for authentication to work
139+ final String remote = 'https://$token @$hostname /$orgName /$projectName ' ;
101140 await framework.pushRef (
102141 fromRef: await featureBranchName,
103142 toRef: await featureBranchName,
104- remote: framework.mirrorRemote ! .url ,
143+ remote: remote ,
105144 );
106145 }
107146
@@ -123,7 +162,7 @@ This PR was generated by `flutter update-packages --force-upgrade`.
123162 'https' ,
124163 '--with-token' ,
125164 ],
126- stdin: token,
165+ stdin: '$ token \n ' ,
127166 );
128167 }
129168
@@ -151,6 +190,8 @@ This PR was generated by `flutter update-packages --force-upgrade`.
151190 '$orgName :${await featureBranchName }' ,
152191 '--base' ,
153192 base ,
193+ '--label' ,
194+ 'tool' ,
154195 if (draft)
155196 '--draft' ,
156197 ],
@@ -165,13 +206,16 @@ This PR was generated by `flutter update-packages --force-upgrade`.
165206 ]);
166207 }
167208
168- Future <void > cli (
209+ /// Run a sub-process with the GitHub CLI client.
210+ ///
211+ /// Will return STDOUT of the sub-process.
212+ Future <String > cli (
169213 List <String > args, {
170214 bool allowFailure = false ,
171215 String ? stdin,
172216 String ? workingDirectory,
173217 }) async {
174- print ('Executing "$githubClient ${args .join (' ' )}" in $workingDirectory ' );
218+ log ('Executing "$githubClient ${args .join (' ' )}" in $workingDirectory ' );
175219 final io.Process process = await processManager.start (
176220 < String > [githubClient, ...args],
177221 workingDirectory: workingDirectory,
@@ -203,6 +247,36 @@ This PR was generated by `flutter update-packages --force-upgrade`.
203247 args,
204248 );
205249 }
206- print (stdout);
250+ log (stdout);
251+ return stdout;
252+ }
253+
254+ Future <bool > hasOpenPrs () async {
255+ // gh pr list --author christopherfujino --repo flutter/flutter --state open --json number
256+ final String openPrString = await cli (< String > [
257+ 'pr' ,
258+ 'list' ,
259+ '--author' ,
260+ githubUsername,
261+ '--repo' ,
262+ 'flutter/flutter' ,
263+ '--state' ,
264+ 'open' ,
265+ // We are only interested in pub rolls, not devicelab flaky PRs
266+ '--label' ,
267+ 'tool' ,
268+ // Return structured JSON with the PR numbers of open PRs
269+ '--json' ,
270+ 'number' ,
271+ ]);
272+
273+ // This will be an array of objects, one for each open PR.
274+ final List <Object ?> openPrs = json.decode (openPrString) as List <Object ?>;
275+
276+ if (openPrs.isNotEmpty) {
277+ log ('$githubUsername already has open tool PRs:\n $openPrs ' );
278+ return true ;
279+ }
280+ return false ;
207281 }
208282}
0 commit comments