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

Added some feature and fix some issues with git urls #5

Merged
merged 1 commit into from
Sep 13, 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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ and this project adheres to [Semantic Versioning].

- /

## [1.3.0] - 2023-09-13

### Added

- Improved validation for git urls, now can accept urls without ".git" for example

### Fixed

- Now can catch all git exceptions and show in terminal
- If the folder doesn't exists stop the process and show a message

## [1.2.0] - 2023-09-12

### Changed
Expand Down
5 changes: 3 additions & 2 deletions lib/config/app_config.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:dotenv/dotenv.dart';

import '../constants/constants.dart' as constants;
import '../exceptions/exceptions.dart';
import '../exceptions/config_exceptions.dart';
import '../models/config_property.dart';
import '../utils/string_utils.dart';

Expand Down Expand Up @@ -56,5 +56,6 @@ class AppConfig {
() => constants.DEFAULT_CONFIG_SECRET_PATH,
);

int get maxDurationInMin => _dotEnv[ConfigProperty.maxDurationInMin.value]?.toInt(3) ?? 3;
int get maxDurationInMin =>
_dotEnv[ConfigProperty.maxDurationInMin.value]?.toInt(3) ?? 3;
}
12 changes: 12 additions & 0 deletions lib/exceptions/config_exceptions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// coverage:ignore-file
import 'exceptions.dart';

abstract class ConfigException extends AppException {
const ConfigException(super.message);
}

class ConfigPropertyMissingException extends ConfigException {
const ConfigPropertyMissingException({
required final String property,
}) : super('"$property" property is not defined int the .env file');
}
50 changes: 1 addition & 49 deletions lib/exceptions/exceptions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,52 +6,4 @@ abstract class AppException implements Exception {

@override
String toString() => _message;
}

class ExecutableNotFoundInPathException extends AppException {
const ExecutableNotFoundInPathException(
final String executable,
) : super("$executable is not found in the PATH. Check is it installed or in the PATH");
}

class ConfigPropertyMissingException extends AppException {
const ConfigPropertyMissingException({
required final String property,
}) : super('"$property" property is not defined int the .env file');
}

abstract class GitException extends AppException {
const GitException(super.message);
}

class InvalidValidGitPathException extends GitException {
const InvalidValidGitPathException({required final String path})
: super('"$path" path is and invalid git repository');
}

class IncorrectTopLevelGitPathException extends GitException {
const IncorrectTopLevelGitPathException({
required final String path,
}) : super('"$path" path is not in the top level of the git repository');
}

class InvalidGitLocalBranchException extends GitException {
InvalidGitLocalBranchException({
required final String branch,
}) : super(
'The selected branch "$branch" is not valid for the git repository');
}

class InvalidGitRemoteBranchException extends GitException {
InvalidGitRemoteBranchException({
required final String branch,
}) : super(
'The selected branch "$branch" is not in the remote git repository');
}

class GitRemoteToManyTimeException extends GitException {
GitRemoteToManyTimeException({
required final int minutes,
}) : super(
"Attempting to connect to remote repository took longer than expected ($minutes min)");
}
}
18 changes: 18 additions & 0 deletions lib/exceptions/file_system_exceptions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// coverage:ignore-file
import 'exceptions.dart';

abstract class FileSystemException extends AppException {
const FileSystemException(super.message);
}

class ExecutableNotFoundInPathException extends FileSystemException {
const ExecutableNotFoundInPathException(
final String executable,
) : super("$executable is not found in the PATH. Check is it installed or in the PATH");
}

class FolderNotFoundException extends FileSystemException {
FolderNotFoundException({
required String path,
}) : super('The current path "$path" not exists');
}
25 changes: 25 additions & 0 deletions lib/exceptions/git_exceptions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// coverage:ignore-file
import 'package:process_run/process_run.dart';

import 'exceptions.dart';

abstract class GitException extends AppException {
const GitException(super.message);
}

class IncorrectTopLevelGitPathException extends GitException {
const IncorrectTopLevelGitPathException({
required final String path,
}) : super('"$path" path is not in the top level of the repository');
}

class GitShellException extends GitException {
GitShellException(ShellException e) : super(e.result?.errText ?? e.message);
}

class GitRemoteToManyTimeException extends GitException {
GitRemoteToManyTimeException({
required final int minutes,
}) : super(
"Attempting to connect to remote repository took longer than expected ($minutes min)");
}
3 changes: 3 additions & 0 deletions lib/exceptions/handler/handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import 'dart:async';

import '../../config/logger.dart';
import '../exceptions.dart';
import '../git_exceptions.dart';

Future<void> handle(FutureOr<void> Function() function) async {
try {
await function();
} on GitException catch (e) {
log.e("Problems related to git found\n$e");
} on AppException catch (e) {
log.e(e);
} catch (e, s) {
Expand Down
68 changes: 42 additions & 26 deletions lib/services/repo_service.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import 'dart:io';

import 'package:path/path.dart' as path;
import 'package:process_run/process_run.dart';

import '../config/app_config.dart';
import '../config/logger.dart';
import '../constants/constants.dart' as constants;
import '../exceptions/exceptions.dart';
import '../exceptions/file_system_exceptions.dart';
import '../exceptions/git_exceptions.dart';
import '../utils/git_utils.dart';
import '../utils/string_utils.dart';
import 'shell_service.dart';
Expand All @@ -15,8 +18,13 @@ class RepoService {

String _gitPath = "";
bool _isCloned = false;
final String _defaultReposFolder;

RepoService(this._appConfig, this._shellService);
RepoService(
this._appConfig,
this._shellService, {
String? defaultParentFolder,
}) : _defaultReposFolder = defaultParentFolder ?? constants.REPOS_FOLDER;

Future<void> _overrideConfigGitBranchWithCurrentOnEmpty() async {
if (_appConfig.gitBranch != "") return;
Expand All @@ -36,8 +44,8 @@ class RepoService {
await _shellService.runScript(
constants.GIT_CHECKOUT.format([safeBranch]),
);
} on ShellException {
throw InvalidGitLocalBranchException(branch: branch);
} on ShellException catch (e) {
throw GitShellException(e);
}
}

Expand All @@ -46,19 +54,22 @@ class RepoService {
log.i('Getting latests changes for "$branch" branch');
final duration = Duration(minutes: _appConfig.maxDurationInMin);
try {
await _shellService.runScript(
await _shellService
.runScript(
constants.GIT_REFRESH_BRANCH.format([
branch,
_getRemoteConfig(),
]),
).timeout(duration, onTimeout: () {
_shellService.dispose();
throw GitRemoteToManyTimeException(
minutes: duration.inMinutes
);
});
} on ShellException {
throw InvalidGitRemoteBranchException(branch: branch);
)
.timeout(
duration,
onTimeout: () {
_shellService.dispose();
throw GitRemoteToManyTimeException(minutes: duration.inMinutes);
},
);
} on ShellException catch (e) {
throw GitShellException(e);
}
}

Expand All @@ -79,17 +90,17 @@ class RepoService {
path: _gitPath,
);
}
} on ShellException {
throw InvalidValidGitPathException(
path: _gitPath,
);
} on ShellException catch (e) {
throw GitShellException(e);
}
}

Future<bool> _tryCloning({
Future<bool> _tryCloneInPath({
required String gitUrl,
required String gitPath,
}) async {
if ((Directory(_gitPath).existsSync())) return false;

try {
await _shellService.runScript(constants.GIT_CLONE.format([
gitUrl,
Expand All @@ -98,14 +109,12 @@ class RepoService {
]));
log.i("Successfully cloned at: $gitPath");
return true;
} on ShellException {
log.w('The path "$gitPath" exists and is not empty');
return false;
} on ShellException catch (e) {
throw GitShellException(e);
}
}

Future<void> _validateGitUrlAndClone() async {

final bool isGitUrl = isValidGitUrl(_appConfig.gitRepoPath);

if (!isGitUrl) {
Expand All @@ -115,24 +124,31 @@ class RepoService {
}

_gitPath = path.absolute(
constants.REPOS_FOLDER,
_defaultReposFolder,
extractGitPath(_appConfig.gitRepoPath),
);

_isCloned = await _tryCloning(
_isCloned = await _tryCloneInPath(
gitUrl: _appConfig.gitRepoPath,
gitPath: _gitPath,
);
}

void _validateGitPathAndMoveShell() {
if (!Directory(_gitPath).existsSync()) {
throw FolderNotFoundException(path: _gitPath);
}
_shellService.moveShellTo(_gitPath);
}

Future<String> setup() async {
_shellService.checkExecutable(constants.GIT);

log.i("Checking properties for: ${_appConfig.gitRepoPath}");

await _validateGitUrlAndClone();

_shellService.moveShellTo(_gitPath);
_validateGitPathAndMoveShell();
await _validateIfPathIsGitRepo();
await _overrideConfigGitBranchWithCurrentOnEmpty();

Expand All @@ -151,7 +167,7 @@ class RepoService {

String _getRemoteConfig() {
final config = "{}".format([
!_appConfig.gitSSLEnabled ? constants.GIT_SSL_VERIFY_FALSE : ""
!_appConfig.gitSSLEnabled ? constants.GIT_SSL_VERIFY_FALSE : "",
]);
if (config.isEmpty) return "";
return "-c $config";
Expand Down
2 changes: 1 addition & 1 deletion lib/services/shell_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:process_run/process_run.dart';

import '../exceptions/exceptions.dart';
import '../exceptions/file_system_exceptions.dart';

class ShellService {
Shell _shell;
Expand Down
24 changes: 13 additions & 11 deletions lib/utils/git_utils.dart
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
final _validUrlRegex = RegExp(
r"^(([A-Za-z0-9]+@|http(|s)\:\/\/)|(http(|s)\:\/\/[A-Za-z0-9]+@))([A-Za-z0-9.]+(:\d+)?)(?::|\/)([\d\/\w-]+?)(\.git){0,1}(/?)$",
caseSensitive: false,
multiLine: false,
);

bool isValidGitUrl(String url) {
final regExp = RegExp(
"((http|git|ssh|http(s)|file|\\/?)|(git@[\\w\\.]+))(:(\\/\\/)?)([\\w\\.@\\:/\\-~]+)(\\.git)(\\/)?",
caseSensitive: false,
multiLine: false,
);
return regExp.hasMatch(url);
return _validUrlRegex.hasMatch(url);
}

String extractGitPath(String url) {
if (url.endsWith(".git")) {
url = url.substring(0, url.length - 4);
}
RegExp httpRegex = RegExp(r"https?://[^/]+/");
url = url.replaceFirst(httpRegex, "");

RegExp sshRegex = RegExp(r"^([^@]+@[^:/]+:)");
url = url.replaceFirst(sshRegex, "");
return url;
// Remove "http://" or "https://"
url = url.replaceFirst(RegExp(r"https?:\/\/"), "");
// Remove any@
url = url.replaceFirst(RegExp(r"^[^@]+@"), "");
// Remove invalid windows caracteres
return url.replaceAll(RegExp(r'[\\/:*?"<>|]'), "/");
}
Empty file.
4 changes: 2 additions & 2 deletions test/config/app_config_test.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import 'package:config_props_extractor/config/app_config.dart';
import 'package:config_props_extractor/exceptions/exceptions.dart';
import 'package:config_props_extractor/constants/constants.dart' as constants;
import 'package:config_props_extractor/exceptions/config_exceptions.dart';
import 'package:config_props_extractor/models/config_property.dart';
import 'package:config_props_extractor/utils/string_utils.dart';
import 'package:dotenv/dotenv.dart';
import 'package:test/test.dart';
import 'package:config_props_extractor/constants/constants.dart' as constants;

void main() {
late DotEnv dotEnv;
Expand Down
Loading