diff --git a/README.md b/README.md index db5a6f88..6e482727 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,7 @@ using the matched rule and runs it. Rules enabled by default are as follows: * `react_native_command_unrecognized` – fixes unrecognized `react-native` commands; * `remove_trailing_cedilla` – remove trailling cedillas `รง`, a common typo for european keyboard layouts; * `rm_dir` – adds `-rf` when you trying to remove directory; +* `scm_correction` – corrects wrong scm like `hg log` to `git log`; * `sed_unterminated_s` – adds missing '/' to `sed`'s `s` commands; * `sl_ls` – changes `sl` to `ls`; * `ssh_known_hosts` – removes host from `known_hosts` on warning; diff --git a/tests/rules/test_scm_correction.py b/tests/rules/test_scm_correction.py new file mode 100644 index 00000000..7f02b905 --- /dev/null +++ b/tests/rules/test_scm_correction.py @@ -0,0 +1,46 @@ +import pytest +from thefuck.rules.scm_correction import match, get_new_command +from tests.utils import Command + + +@pytest.fixture +def get_actual_scm_mock(mocker): + return mocker.patch('thefuck.rules.scm_correction._get_actual_scm', + return_value=None) + + +@pytest.mark.parametrize('script, stderr, actual_scm', [ + ('git log', 'fatal: Not a git repository ' + '(or any of the parent directories): .git', + 'hg'), + ('hg log', "abort: no repository found in '/home/nvbn/exp/thefuck' " + "(.hg not found)!", + 'git')]) +def test_match(get_actual_scm_mock, script, stderr, actual_scm): + get_actual_scm_mock.return_value = actual_scm + assert match(Command(script, stderr=stderr)) + + +@pytest.mark.parametrize('script, stderr, actual_scm', [ + ('git log', '', 'hg'), + ('git log', 'fatal: Not a git repository ' + '(or any of the parent directories): .git', + None), + ('hg log', "abort: no repository found in '/home/nvbn/exp/thefuck' " + "(.hg not found)!", + None), + ('not-scm log', "abort: no repository found in '/home/nvbn/exp/thefuck' " + "(.hg not found)!", + 'git')]) +def test_not_match(get_actual_scm_mock, script, stderr, actual_scm): + get_actual_scm_mock.return_value = actual_scm + assert not match(Command(script, stderr=stderr)) + + +@pytest.mark.parametrize('script, actual_scm, result', [ + ('git log', 'hg', 'hg log'), + ('hg log', 'git', 'git log')]) +def test_get_new_command(get_actual_scm_mock, script, actual_scm, result): + get_actual_scm_mock.return_value = actual_scm + new_command = get_new_command(Command(script)) + assert new_command == result diff --git a/thefuck/rules/scm_correction.py b/thefuck/rules/scm_correction.py new file mode 100644 index 00000000..0b2fc7e5 --- /dev/null +++ b/thefuck/rules/scm_correction.py @@ -0,0 +1,32 @@ +from thefuck.utils import for_app, memoize +from thefuck.system import Path + +path_to_scm = { + '.git': 'git', + '.hg': 'hg', +} + +wrong_scm_patterns = { + 'git': 'fatal: Not a git repository', + 'hg': 'abort: no repository found', +} + + +@memoize +def _get_actual_scm(): + for path, scm in path_to_scm.items(): + if Path(path).is_dir(): + return scm + + +@for_app(*wrong_scm_patterns.keys()) +def match(command): + scm = command.script_parts[0] + pattern = wrong_scm_patterns[scm] + + return pattern in command.stderr and _get_actual_scm() + + +def get_new_command(command): + scm = _get_actual_scm() + return u' '.join([scm] + command.script_parts[1:])