Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(shorebird_cli): ShorebirdVersionManager use Git wrapper #1055

Merged
merged 1 commit into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 8 additions & 39 deletions packages/shorebird_cli/lib/src/shorebird_version_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'dart:io';

import 'package:path/path.dart' as p;
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/git.dart';

/// A reference to a [ShorebirdVersionManager] instance.
final shorebirdVersionManagerRef = create(ShorebirdVersionManager.new);
Expand All @@ -20,7 +20,6 @@ class ShorebirdVersionManager {
/// Whether the current version of Shorebird is the latest available.
Future<bool> isShorebirdVersionCurrent() async {
final currentVersion = await fetchCurrentGitHash();

final latestVersion = await fetchLatestGitHash();

return currentVersion == latestVersion;
Expand All @@ -31,39 +30,17 @@ class ShorebirdVersionManager {
/// Exits if HEAD isn't pointing to a branch, or there is no upstream.
Future<String> fetchLatestGitHash() async {
// Fetch upstream branch's commits and tags
await process.run(
'git',
['fetch', '--tags'],
workingDirectory: _workingDirectory,
);
await git.fetch(directory: _workingDirectory, args: ['--tags']);
// Get the latest commit revision of the upstream
return _gitRevParse('@{upstream}');
return git.revParse(revision: '@{upstream}', directory: _workingDirectory);
}

/// Returns the local HEAD shorebird hash.
///
/// Exits if HEAD isn't pointing to a branch, or there is no upstream.
Future<String> fetchCurrentGitHash() async {
// Get the commit revision of HEAD
return _gitRevParse('HEAD');
}

Future<String> _gitRevParse(String revision) async {
// Get the commit revision of HEAD
final result = await process.run(
'git',
['rev-parse', '--verify', revision],
workingDirectory: _workingDirectory,
);
if (result.exitCode != 0) {
throw ProcessException(
'git',
['rev-parse', '--verify', revision],
'${result.stderr}',
result.exitCode,
);
}
return '${result.stdout}'.trim();
return git.revParse(revision: 'HEAD', directory: _workingDirectory);
}

/// Attempts a hard reset to the given revision.
Expand All @@ -74,18 +51,10 @@ class ShorebirdVersionManager {
Future<void> attemptReset({
required String newRevision,
}) async {
final result = await process.run(
'git',
['reset', '--hard', newRevision],
workingDirectory: _workingDirectory,
return git.reset(
revision: newRevision,
directory: _workingDirectory,
args: ['--hard'],
);
if (result.exitCode != 0) {
throw ProcessException(
'git',
['reset', '--hard', newRevision],
'${result.stderr}',
result.exitCode,
);
}
}
}
157 changes: 88 additions & 69 deletions packages/shorebird_cli/test/src/shorebird_version_manager_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,119 +3,119 @@ import 'dart:io';
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/git.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:test/test.dart';

class _MockProcessResult extends Mock implements ShorebirdProcessResult {}

class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
class _MockGit extends Mock implements Git {}

void main() {
group(ShorebirdVersionManager, () {
const currentShorebirdRevision = 'revision-1';
const newerShorebirdRevision = 'revision-2';

late ShorebirdProcessResult fetchCurrentVersionResult;
late ShorebirdProcessResult fetchLatestVersionResult;
late ShorebirdProcessResult hardResetResult;
late ShorebirdProcess shorebirdProcess;
late Git git;
late ShorebirdVersionManager shorebirdVersionManager;

R runWithOverrides<R>(R Function() body) {
return runScoped(
body,
values: {
engineConfigRef.overrideWith(() => const EngineConfig.empty()),
processRef.overrideWith(() => shorebirdProcess),
gitRef.overrideWith(() => git),
},
);
}

setUp(() {
fetchCurrentVersionResult = _MockProcessResult();
fetchLatestVersionResult = _MockProcessResult();
hardResetResult = _MockProcessResult();
shorebirdProcess = _MockShorebirdProcess();
git = _MockGit();
shorebirdVersionManager = ShorebirdVersionManager();

when(
() => shorebirdProcess.run(
'git',
['rev-parse', '--verify', 'HEAD'],
workingDirectory: any(named: 'workingDirectory'),
),
).thenAnswer((_) async => fetchCurrentVersionResult);
when(
() => shorebirdProcess.run(
'git',
['rev-parse', '--verify', '@{upstream}'],
workingDirectory: any(named: 'workingDirectory'),
() => git.fetch(
directory: any(named: 'directory'),
args: any(named: 'args'),
),
).thenAnswer((_) async => fetchLatestVersionResult);
).thenAnswer((_) async => {});
when(
() => shorebirdProcess.run(
'git',
['fetch', '--tags'],
workingDirectory: any(named: 'workingDirectory'),
() => git.revParse(
revision: any(named: 'revision'),
directory: any(named: 'directory'),
),
).thenAnswer((_) async => _MockProcessResult());
).thenAnswer((_) async => currentShorebirdRevision);
when(
() => shorebirdProcess.run(
'git',
['reset', '--hard', 'HEAD'],
workingDirectory: any(named: 'workingDirectory'),
() => git.reset(
revision: any(named: 'revision'),
directory: any(named: 'directory'),
args: any(named: 'args'),
),
).thenAnswer((_) async => hardResetResult);

when(
() => fetchCurrentVersionResult.exitCode,
).thenReturn(ExitCode.success.code);
when(
() => fetchCurrentVersionResult.stdout,
).thenReturn(currentShorebirdRevision);
when(
() => fetchLatestVersionResult.exitCode,
).thenReturn(ExitCode.success.code);
when(
() => fetchLatestVersionResult.stdout,
).thenReturn(currentShorebirdRevision);
when(() => hardResetResult.exitCode).thenReturn(ExitCode.success.code);
).thenAnswer((_) async {});
});

group('isShorebirdVersionCurrent', () {
test('returns true if current and latest git hashes match', () async {
when(
() => fetchCurrentVersionResult.stdout,
).thenReturn(currentShorebirdRevision);
when(
() => fetchLatestVersionResult.stdout,
).thenReturn(currentShorebirdRevision);

expect(
await runWithOverrides(
shorebirdVersionManager.isShorebirdVersionCurrent,
),
isTrue,
);
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(
'returns false if current and latest git hashes differ',
() async {
when(
() => fetchCurrentVersionResult.stdout,
).thenReturn(currentShorebirdRevision);
when(
() => fetchLatestVersionResult.stdout,
).thenReturn(newerShorebirdRevision);
() => 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);
},
);

Expand All @@ -124,9 +124,18 @@ void main() {
() async {
const errorMessage = 'oh no!';
when(
() => fetchCurrentVersionResult.exitCode,
).thenReturn(ExitCode.software.code);
when(() => fetchCurrentVersionResult.stderr).thenReturn(errorMessage);
() => git.revParse(
revision: any(named: 'revision'),
directory: any(named: 'directory'),
),
).thenThrow(
ProcessException(
'git',
['rev-parse', 'HEAD'],
errorMessage,
ExitCode.software.code,
),
);

expect(
runWithOverrides(
Expand Down Expand Up @@ -154,13 +163,23 @@ void main() {
});

test(
'''throws ProcessException when git command exits with code other than 0''',
'throws ProcessException when git command exits with code other than 0',
() async {
const errorMessage = 'oh no!';
when(
() => hardResetResult.exitCode,
).thenReturn(ExitCode.software.code);
when(() => hardResetResult.stderr).thenReturn(errorMessage);
() => 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(
Expand Down