Skip to content

Commit

Permalink
refactor(shorebird_cli): ShorebirdVersionManager use Git wrapper (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
felangel authored Aug 8, 2023
1 parent 19a9ff7 commit 1f3c7e7
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 108 deletions.
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

0 comments on commit 1f3c7e7

Please sign in to comment.