forked from flutter/cocoon
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Automatic Revert (Part 1): Base services needed for the automated rev…
…ert. (flutter#3083) This is the first part of the changes for Automated revert. These are the base classes that actually perform the revert. There are two methods that can be used. Unfortunately the github graphql method does not currently work. I have an open issue with github but included the code here anyway. The git command line tools currently perform the revert. It checks out the repo, reverts the commit on the command line and then pushes the commit to github. It then creates a branch with the change which will be auto approved by the fluttergithubbot. Then ultimately merged. *List which issues are fixed by this PR. You must list at least one issue.* Part of flutter/flutter#113867 *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*
- Loading branch information
1 parent
5d3c2be
commit 31f6f44
Showing
13 changed files
with
991 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// Copyright 2023 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
import 'dart:io'; | ||
|
||
import 'package:auto_submit/action/revert_method.dart'; | ||
import 'package:auto_submit/configuration/repository_configuration.dart'; | ||
import 'package:auto_submit/git/cli_command.dart'; | ||
import 'package:auto_submit/git/utilities.dart'; | ||
import 'package:auto_submit/git/git_cli.dart'; | ||
import 'package:auto_submit/git/git_repository_manager.dart'; | ||
import 'package:auto_submit/requests/exceptions.dart'; | ||
import 'package:auto_submit/service/config.dart'; | ||
import 'package:auto_submit/service/github_service.dart'; | ||
import 'package:auto_submit/service/log.dart'; | ||
import 'package:auto_submit/service/revert_issue_body_formatter.dart'; | ||
import 'package:github/github.dart' as github; | ||
import 'package:github/github.dart'; | ||
import 'package:retry/retry.dart'; | ||
|
||
class GitCliRevertMethod implements RevertMethod { | ||
@override | ||
Future<github.PullRequest?> createRevert(Config config, github.PullRequest pullRequest) async { | ||
final github.RepositorySlug slug = pullRequest.base!.repo!.slug(); | ||
final String commitSha = pullRequest.mergeCommitSha!; | ||
// we will need to collect the pr number after the revert request is generated. | ||
|
||
final RepositoryConfiguration repositoryConfiguration = await config.getRepositoryConfiguration(slug); | ||
final String baseBranch = repositoryConfiguration.defaultBranch; | ||
|
||
final String cloneToDirectory = '${slug.name}_$commitSha'; | ||
final GitRepositoryManager gitRepositoryManager = GitRepositoryManager( | ||
slug: slug, | ||
workingDirectory: Directory.current.path, | ||
cloneToDirectory: cloneToDirectory, | ||
gitCli: GitCli(GitAccessMethod.HTTP, CliCommand()), | ||
); | ||
|
||
// The exception is caught by the thrower. | ||
try { | ||
await gitRepositoryManager.cloneRepository(); | ||
await gitRepositoryManager.setupConfig(); | ||
await gitRepositoryManager.revertCommit(baseBranch, commitSha, slug, await config.generateGithubToken(slug)); | ||
} finally { | ||
await gitRepositoryManager.deleteRepository(); | ||
} | ||
|
||
final GitRevertBranchName gitRevertBranchName = GitRevertBranchName(commitSha); | ||
final GithubService githubService = await config.createGithubService(slug); | ||
|
||
const RetryOptions retryOptions = | ||
RetryOptions(delayFactor: Duration(seconds: 1), maxDelay: Duration(seconds: 1), maxAttempts: 4); | ||
|
||
Branch? branch; | ||
// Attempt a few times to get the branch name. This may not be needed. | ||
// Let the exception bubble up from here. | ||
await retryOptions.retry( | ||
() async { | ||
branch = await githubService.getBranch(slug, gitRevertBranchName.branch); | ||
}, | ||
retryIf: (Exception e) => e is NotFoundException, | ||
); | ||
|
||
log.info('found branch ${slug.fullName}/${branch!.name}, safe to create revert request.'); | ||
|
||
final RevertIssueBodyFormatter formatter = RevertIssueBodyFormatter( | ||
slug: slug, | ||
originalPrNumber: pullRequest.number!, | ||
initiatingAuthor: 'ricardoamador', | ||
originalPrTitle: pullRequest.title!, | ||
originalPrBody: pullRequest.body!, | ||
).format; | ||
|
||
log.info('Attempting to create pull request with ${slug.fullName}/${gitRevertBranchName.branch}.'); | ||
final github.PullRequest revertPullRequest = await githubService.createPullRequest( | ||
slug: slug, | ||
title: formatter.revertPrTitle, | ||
head: gitRevertBranchName.branch, | ||
base: 'main', | ||
draft: false, | ||
body: formatter.revertPrBody, | ||
); | ||
|
||
log.info('pull request number is: ${slug.fullName}/${revertPullRequest.number}'); | ||
|
||
return revertPullRequest; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright 2023 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
import 'package:auto_submit/service/config.dart'; | ||
import 'package:github/github.dart' as github; | ||
|
||
abstract class RevertMethod { | ||
// Allows substitution of the method of creating the revert request. | ||
Future<Object?> createRevert(Config config, github.PullRequest pullRequest); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// Copyright 2023 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
import 'dart:io'; | ||
|
||
class CliCommand { | ||
CliCommand(); | ||
|
||
/// Method runs a single command in a shell. | ||
Future<ProcessResult> runCliCommand({ | ||
required String executable, | ||
required List<String> arguments, | ||
bool throwOnError = true, | ||
String? workingDirectory, | ||
}) async { | ||
final process = await Process.start( | ||
executable, | ||
arguments, | ||
workingDirectory: workingDirectory, | ||
runInShell: true, | ||
mode: ProcessStartMode.normal, | ||
); | ||
|
||
final result = await Future.wait([ | ||
process.exitCode, | ||
process.stdout.transform(const SystemEncoding().decoder).join(), | ||
process.stderr.transform(const SystemEncoding().decoder).join(), | ||
]); | ||
|
||
final ProcessResult processResult = ProcessResult( | ||
process.pid, | ||
result[0] as int, | ||
result[1] as String, | ||
result[2] as String, | ||
); | ||
|
||
if (throwOnError) { | ||
if (processResult.exitCode != 0) { | ||
final Map<String, String> outputs = { | ||
if (processResult.stdout != null) 'Standard out': processResult.stdout.toString().trim(), | ||
if (processResult.stderr != null) 'Standard error': processResult.stderr.toString().trim(), | ||
}..removeWhere((k, v) => v.isEmpty); | ||
|
||
String errorMessage; | ||
if (outputs.isEmpty) { | ||
errorMessage = 'Unknown error.'; | ||
} else { | ||
errorMessage = outputs.entries.map((entry) => '${entry.key}\n${entry.value}').join('\n'); | ||
} | ||
|
||
throw ProcessException(executable, arguments, errorMessage, processResult.exitCode); | ||
} | ||
} | ||
|
||
return processResult; | ||
} | ||
} |
Oops, something went wrong.