diff --git a/README.md b/README.md index 0a10605..d5e161c 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,7 @@ following rules are enabled by default: * `git_branch_0flag` – fixes commands such as `git branch 0v` and `git branch 0r` removing the created branch; * `git_checkout` – fixes branch name or creates new branch; * `git_clone_git_clone` – replaces `git clone git clone ...` with `git clone ...` +* `git_clone_missing` – adds `git clone` to URLs that appear to link to a git repository. * `git_commit_add` – offers `git commit -a ...` or `git commit -p ...` after previous commit if it failed because nothing was staged; * `git_commit_amend` – offers `git commit --amend` after previous commit; * `git_commit_reset` – offers `git reset HEAD~` after previous commit; diff --git a/tests/rules/test_git_clone_missing.py b/tests/rules/test_git_clone_missing.py new file mode 100644 index 0000000..dcd95fd --- /dev/null +++ b/tests/rules/test_git_clone_missing.py @@ -0,0 +1,50 @@ +import pytest +from thefuck.rules.git_clone_missing import match, get_new_command +from thefuck.types import Command + +valid_urls = [ + 'https://github.com/nvbn/thefuck.git', + 'https://github.com/nvbn/thefuck', + 'http://github.com/nvbn/thefuck.git', + 'git@github.com:nvbn/thefuck.git', + 'git@github.com:nvbn/thefuck', + 'ssh://git@github.com:nvbn/thefuck.git', +] +invalid_urls = [ + '', # No command + 'notacommand', # Command not found + 'ssh git@github.com:nvbn/thefrick.git', # ssh command, not a git clone + 'git clone foo', # Valid clone + 'git clone https://github.com/nvbn/thefuck.git', # Full command + 'github.com/nvbn/thefuck.git', # Missing protocol + 'github.com:nvbn/thefuck.git', # SSH missing username + 'git clone git clone ssh://git@github.com:nvbn/thefrick.git', # 2x clone + 'https:/github.com/nvbn/thefuck.git' # Bad protocol +] +outputs = [ + 'No such file or directory', + 'not found', + 'is not recognised as', +] + + +@pytest.mark.parametrize('cmd', valid_urls) +@pytest.mark.parametrize('output', outputs) +def test_match(cmd, output): + c = Command(cmd, output) + assert match(c) + + +@pytest.mark.parametrize('cmd', invalid_urls) +@pytest.mark.parametrize('output', outputs + ["some other output"]) +def test_not_match(cmd, output): + c = Command(cmd, output) + assert not match(c) + + +@pytest.mark.parametrize('script', valid_urls) +@pytest.mark.parametrize('output', outputs) +def test_get_new_command(script, output): + command = Command(script, output) + new_command = 'git clone ' + script + assert get_new_command(command) == new_command diff --git a/thefuck/rules/git_clone_missing.py b/thefuck/rules/git_clone_missing.py new file mode 100644 index 0000000..bc5c098 --- /dev/null +++ b/thefuck/rules/git_clone_missing.py @@ -0,0 +1,42 @@ +''' +Rule: git_clone_missing + +Correct missing `git clone` command when pasting a git URL + +```sh +>>> https://github.com/nvbn/thefuck.git +git clone https://github.com/nvbn/thefuck.git +``` + +Author: Miguel Guthridge +''' +from six.moves.urllib import parse +from thefuck.utils import which + + +def match(command): + # We want it to be a URL by itself + if len(command.script_parts) != 1: + return False + # Ensure we got the error we expected + if which(command.script_parts[0]) or not ( + 'No such file or directory' in command.output + or 'not found' in command.output + or 'is not recognised as' in command.output + ): + return False + url = parse.urlparse(command.script, scheme='ssh') + # HTTP URLs need a network address + if not url.netloc and url.scheme != 'ssh': + return False + # SSH needs a username and a splitter between the path + if url.scheme == 'ssh' and not ( + '@' in command.script + and ':' in command.script + ): + return False + return url.scheme in ['http', 'https', 'ssh'] + + +def get_new_command(command): + return 'git clone ' + command.script