refactor(shorebird_cli): shorebird upgrade use prune from ShorebirdFlutterManager (#1057)
This commit is contained in:
@@ -3,8 +3,7 @@ import 'dart:io';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:shorebird_cli/src/command.dart';
|
||||
import 'package:shorebird_cli/src/logger.dart';
|
||||
import 'package:shorebird_cli/src/process.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_flutter_manager.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
|
||||
|
||||
/// {@template upgrade_command}
|
||||
@@ -64,7 +63,16 @@ class UpgradeCommand extends ShorebirdCommand {
|
||||
}
|
||||
|
||||
try {
|
||||
await _pruneFlutterOrigin();
|
||||
// Intended to fix an issue caused by a change in our remote branches.
|
||||
// We deleted (origin/shorebird) and created (origin/shorebird/main)
|
||||
//
|
||||
// The error manifested at:
|
||||
// $ shorebird --version
|
||||
// Updating Flutter...
|
||||
// error: cannot lock ref 'refs/remotes/origin/shorebird/main': 'refs/remotes/origin/shorebird' exists; cannot create 'refs/remotes/origin/shorebird/main'
|
||||
// From https://github.com/shorebirdtech/flutter
|
||||
// ! [new branch] shorebird/main -> origin/shorebird/main (unable to update local ref)
|
||||
await shorebirdFlutterManager.pruneRemoteOrigin(revision: latestVersion);
|
||||
} on ProcessException catch (error) {
|
||||
updateProgress.fail();
|
||||
logger.err('Updating failed: ${error.message}');
|
||||
@@ -75,32 +83,4 @@ class UpgradeCommand extends ShorebirdCommand {
|
||||
|
||||
return ExitCode.success.code;
|
||||
}
|
||||
|
||||
// Intended to fix an issue caused by a change in our remote branches.
|
||||
// We deleted (origin/shorebird) and created (origin/shorebird/main)
|
||||
//
|
||||
// The error manifested at:
|
||||
// $ shorebird --version
|
||||
// Updating Flutter...
|
||||
// error: cannot lock ref 'refs/remotes/origin/shorebird/main': 'refs/remotes/origin/shorebird' exists; cannot create 'refs/remotes/origin/shorebird/main'
|
||||
// From https://github.com/shorebirdtech/flutter
|
||||
// ! [new branch] shorebird/main -> origin/shorebird/main (unable to update local ref)
|
||||
Future<void> _pruneFlutterOrigin() async {
|
||||
const executable = 'git';
|
||||
final args = ['remote', 'prune', 'origin'];
|
||||
final result = await process.run(
|
||||
executable,
|
||||
args,
|
||||
workingDirectory: shorebirdEnv.flutterDirectory.path,
|
||||
);
|
||||
|
||||
if (result.exitCode != 0) {
|
||||
throw ProcessException(
|
||||
executable,
|
||||
args,
|
||||
'${result.stderr}',
|
||||
result.exitCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,13 @@ class ShorebirdFlutterManager {
|
||||
static const String flutterGitUrl =
|
||||
'https://github.com/shorebirdtech/flutter.git';
|
||||
|
||||
String _workingDirectory({String? revision}) {
|
||||
revision ??= shorebirdEnv.flutterRevision;
|
||||
return p.join(shorebirdEnv.flutterDirectory.parent.path, revision);
|
||||
}
|
||||
|
||||
Future<void> installRevision({required String revision}) async {
|
||||
final targetDirectory = Directory(
|
||||
p.join(shorebirdEnv.flutterDirectory.parent.path, revision),
|
||||
);
|
||||
final targetDirectory = Directory(_workingDirectory(revision: revision));
|
||||
if (targetDirectory.existsSync()) return;
|
||||
|
||||
// Clone the Shorebird Flutter repo into the target directory.
|
||||
@@ -40,9 +43,14 @@ class ShorebirdFlutterManager {
|
||||
);
|
||||
|
||||
// Checkout the correct revision.
|
||||
await git.checkout(
|
||||
directory: targetDirectory.path,
|
||||
revision: revision,
|
||||
await git.checkout(directory: targetDirectory.path, revision: revision);
|
||||
}
|
||||
|
||||
/// Prunes stale remote branches from the repository.
|
||||
Future<void> pruneRemoteOrigin({String? revision}) async {
|
||||
return git.remotePrune(
|
||||
name: 'origin',
|
||||
directory: _workingDirectory(revision: revision),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,20 +5,16 @@ import 'package:mocktail/mocktail.dart';
|
||||
import 'package:scoped/scoped.dart';
|
||||
import 'package:shorebird_cli/src/commands/commands.dart';
|
||||
import 'package:shorebird_cli/src/logger.dart';
|
||||
import 'package:shorebird_cli/src/process.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_flutter_manager.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class _MockLogger extends Mock implements Logger {}
|
||||
|
||||
class _MockProcessResult extends Mock implements ShorebirdProcessResult {}
|
||||
|
||||
class _MockProgress extends Mock implements Progress {}
|
||||
|
||||
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
|
||||
|
||||
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
|
||||
class _MockShorebirdFlutterManager extends Mock
|
||||
implements ShorebirdFlutterManager {}
|
||||
|
||||
class _MockShorebirdVersionManager extends Mock
|
||||
implements ShorebirdVersionManager {}
|
||||
@@ -29,9 +25,7 @@ void main() {
|
||||
|
||||
group('upgrade', () {
|
||||
late Logger logger;
|
||||
late ShorebirdProcessResult pruneFlutterOriginResult;
|
||||
late ShorebirdProcess shorebirdProcess;
|
||||
late ShorebirdEnv shorebirdEnv;
|
||||
late ShorebirdFlutterManager shorebirdFlutterManager;
|
||||
late ShorebirdVersionManager shorebirdVersionManager;
|
||||
late UpgradeCommand command;
|
||||
|
||||
@@ -40,8 +34,9 @@ void main() {
|
||||
body,
|
||||
values: {
|
||||
loggerRef.overrideWith(() => logger),
|
||||
processRef.overrideWith(() => shorebirdProcess),
|
||||
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
|
||||
shorebirdFlutterManagerRef.overrideWith(
|
||||
() => shorebirdFlutterManager,
|
||||
),
|
||||
shorebirdVersionManagerRef.overrideWith(
|
||||
() => shorebirdVersionManager,
|
||||
),
|
||||
@@ -54,15 +49,15 @@ void main() {
|
||||
final progressLogs = <String>[];
|
||||
|
||||
logger = _MockLogger();
|
||||
pruneFlutterOriginResult = _MockProcessResult();
|
||||
shorebirdProcess = _MockShorebirdProcess();
|
||||
shorebirdEnv = _MockShorebirdEnv();
|
||||
shorebirdFlutterManager = _MockShorebirdFlutterManager();
|
||||
shorebirdVersionManager = _MockShorebirdVersionManager();
|
||||
command = runWithOverrides(UpgradeCommand.new);
|
||||
|
||||
when(
|
||||
() => shorebirdEnv.flutterDirectory,
|
||||
).thenReturn(Directory('flutter'));
|
||||
() => shorebirdFlutterManager.pruneRemoteOrigin(
|
||||
revision: any(named: 'revision'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
when(
|
||||
shorebirdVersionManager.fetchCurrentGitHash,
|
||||
).thenAnswer((_) async => currentShorebirdRevision);
|
||||
@@ -75,17 +70,6 @@ void main() {
|
||||
),
|
||||
).thenAnswer((_) async => {});
|
||||
|
||||
when(
|
||||
() => shorebirdProcess.run(
|
||||
'git',
|
||||
['remote', 'prune', 'origin'],
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
),
|
||||
).thenAnswer((_) async => pruneFlutterOriginResult);
|
||||
when(
|
||||
() => pruneFlutterOriginResult.exitCode,
|
||||
).thenReturn(ExitCode.success.code);
|
||||
|
||||
when(() => progress.complete(any())).thenAnswer((_) {
|
||||
final message = _.positionalArguments.elementAt(0) as String?;
|
||||
if (message != null) progressLogs.add(message);
|
||||
@@ -156,12 +140,22 @@ void main() {
|
||||
});
|
||||
|
||||
test('handles errors on failure to prune Flutter branches', () async {
|
||||
when(() => pruneFlutterOriginResult.exitCode).thenReturn(1);
|
||||
const exception = ProcessException('git', ['remote', 'prune'], 'oops');
|
||||
when(
|
||||
() => shorebirdFlutterManager.pruneRemoteOrigin(
|
||||
revision: any(named: 'revision'),
|
||||
),
|
||||
).thenThrow(exception);
|
||||
when(() => logger.progress(any())).thenReturn(_MockProgress());
|
||||
|
||||
final result = await runWithOverrides(command.run);
|
||||
|
||||
expect(result, equals(ExitCode.software.code));
|
||||
verify(
|
||||
() => shorebirdFlutterManager.pruneRemoteOrigin(
|
||||
revision: newerShorebirdRevision,
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:scoped/scoped.dart';
|
||||
@@ -14,6 +15,7 @@ class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
|
||||
|
||||
void main() {
|
||||
group(ShorebirdFlutterManager, () {
|
||||
const flutterRevision = 'flutter-revision';
|
||||
late Directory shorebirdRoot;
|
||||
late Directory flutterDirectory;
|
||||
late Git git;
|
||||
@@ -50,7 +52,14 @@ void main() {
|
||||
revision: any(named: 'revision'),
|
||||
),
|
||||
).thenAnswer((_) async => {});
|
||||
when(
|
||||
() => git.remotePrune(
|
||||
name: any(named: 'name'),
|
||||
directory: any(named: 'directory'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
when(() => shorebirdEnv.flutterDirectory).thenReturn(flutterDirectory);
|
||||
when(() => shorebirdEnv.flutterRevision).thenReturn(flutterRevision);
|
||||
});
|
||||
|
||||
group('installRevision', () {
|
||||
@@ -138,5 +147,68 @@ void main() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('pruneRemoteOrigin', () {
|
||||
test('completes when git command exits with code 0', () async {
|
||||
await expectLater(
|
||||
runWithOverrides(() => shorebirdFlutterManager.pruneRemoteOrigin()),
|
||||
completes,
|
||||
);
|
||||
verify(
|
||||
() => git.remotePrune(
|
||||
name: 'origin',
|
||||
directory: p.join(flutterDirectory.parent.path, flutterRevision),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('completes when git command exits with code 0 (custom revision)',
|
||||
() async {
|
||||
const customRevision = 'custom-revision';
|
||||
await expectLater(
|
||||
runWithOverrides(
|
||||
() => shorebirdFlutterManager.pruneRemoteOrigin(
|
||||
revision: customRevision,
|
||||
),
|
||||
),
|
||||
completes,
|
||||
);
|
||||
verify(
|
||||
() => git.remotePrune(
|
||||
name: 'origin',
|
||||
directory: p.join(flutterDirectory.parent.path, customRevision),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('throws ProcessException when git command exits non-zero code',
|
||||
() async {
|
||||
const errorMessage = 'oh no!';
|
||||
when(
|
||||
() => git.remotePrune(
|
||||
name: any(named: 'name'),
|
||||
directory: any(named: 'directory'),
|
||||
),
|
||||
).thenThrow(
|
||||
ProcessException(
|
||||
'git',
|
||||
['remote', 'prune', 'origin'],
|
||||
errorMessage,
|
||||
ExitCode.software.code,
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
runWithOverrides(() => shorebirdFlutterManager.pruneRemoteOrigin()),
|
||||
throwsA(
|
||||
isA<ProcessException>().having(
|
||||
(e) => e.message,
|
||||
'message',
|
||||
errorMessage,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -49,6 +49,12 @@ void main() {
|
||||
args: any(named: 'args'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
when(
|
||||
() => git.remotePrune(
|
||||
name: any(named: 'name'),
|
||||
directory: any(named: 'directory'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
});
|
||||
|
||||
group('isShorebirdVersionCurrent', () {
|
||||
@@ -76,48 +82,44 @@ void main() {
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test(
|
||||
'returns false if current and latest git hashes differ',
|
||||
() async {
|
||||
when(
|
||||
() => git.revParse(
|
||||
revision: any(named: 'revision'),
|
||||
directory: any(named: 'directory'),
|
||||
),
|
||||
).thenAnswer((invocation) async {
|
||||
final revision = invocation.namedArguments[#revision] as String;
|
||||
if (revision == 'HEAD') {
|
||||
return currentShorebirdRevision;
|
||||
} else if (revision == '@{upstream}') {
|
||||
return newerShorebirdRevision;
|
||||
}
|
||||
throw UnsupportedError('Unexpected revision: $revision');
|
||||
});
|
||||
test('returns false if current and latest git hashes differ', () async {
|
||||
when(
|
||||
() => git.revParse(
|
||||
revision: any(named: 'revision'),
|
||||
directory: any(named: 'directory'),
|
||||
),
|
||||
).thenAnswer((invocation) async {
|
||||
final revision = invocation.namedArguments[#revision] as String;
|
||||
if (revision == 'HEAD') {
|
||||
return currentShorebirdRevision;
|
||||
} else if (revision == '@{upstream}') {
|
||||
return newerShorebirdRevision;
|
||||
}
|
||||
throw UnsupportedError('Unexpected revision: $revision');
|
||||
});
|
||||
|
||||
expect(
|
||||
await runWithOverrides(
|
||||
shorebirdVersionManager.isShorebirdVersionCurrent,
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
verify(
|
||||
() =>
|
||||
git.fetch(directory: any(named: 'directory'), args: ['--tags']),
|
||||
).called(1);
|
||||
verify(
|
||||
() => git.revParse(
|
||||
revision: 'HEAD',
|
||||
directory: any(named: 'directory'),
|
||||
),
|
||||
).called(1);
|
||||
verify(
|
||||
() => git.revParse(
|
||||
revision: '@{upstream}',
|
||||
directory: any(named: 'directory'),
|
||||
),
|
||||
).called(1);
|
||||
},
|
||||
);
|
||||
expect(
|
||||
await runWithOverrides(
|
||||
shorebirdVersionManager.isShorebirdVersionCurrent,
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
verify(
|
||||
() => git.fetch(directory: any(named: 'directory'), args: ['--tags']),
|
||||
).called(1);
|
||||
verify(
|
||||
() => git.revParse(
|
||||
revision: 'HEAD',
|
||||
directory: any(named: 'directory'),
|
||||
),
|
||||
).called(1);
|
||||
verify(
|
||||
() => git.revParse(
|
||||
revision: '@{upstream}',
|
||||
directory: any(named: 'directory'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test(
|
||||
'throws ProcessException if git command exits with code other than 0',
|
||||
@@ -162,39 +164,37 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'throws ProcessException when git command exits with code other than 0',
|
||||
() async {
|
||||
const errorMessage = 'oh no!';
|
||||
when(
|
||||
() => git.reset(
|
||||
revision: any(named: 'revision'),
|
||||
directory: any(named: 'directory'),
|
||||
args: any(named: 'args'),
|
||||
),
|
||||
).thenThrow(
|
||||
ProcessException(
|
||||
'git',
|
||||
['reset', '--hard', 'HEAD'],
|
||||
errorMessage,
|
||||
ExitCode.software.code,
|
||||
),
|
||||
);
|
||||
test('throws ProcessException when git command exits with non-zero code',
|
||||
() async {
|
||||
const errorMessage = 'oh no!';
|
||||
when(
|
||||
() => git.reset(
|
||||
revision: any(named: 'revision'),
|
||||
directory: any(named: 'directory'),
|
||||
args: any(named: 'args'),
|
||||
),
|
||||
).thenThrow(
|
||||
ProcessException(
|
||||
'git',
|
||||
['reset', '--hard', 'HEAD'],
|
||||
errorMessage,
|
||||
ExitCode.software.code,
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
runWithOverrides(
|
||||
() => shorebirdVersionManager.attemptReset(newRevision: 'HEAD'),
|
||||
expect(
|
||||
runWithOverrides(
|
||||
() => shorebirdVersionManager.attemptReset(newRevision: 'HEAD'),
|
||||
),
|
||||
throwsA(
|
||||
isA<ProcessException>().having(
|
||||
(e) => e.message,
|
||||
'message',
|
||||
errorMessage,
|
||||
),
|
||||
throwsA(
|
||||
isA<ProcessException>().having(
|
||||
(e) => e.message,
|
||||
'message',
|
||||
errorMessage,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user