1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-03-14 14:48:49 +00:00

Merge branch 'master' into 722-git-crash

This commit is contained in:
David 2018-01-06 19:57:24 +00:00
commit ea55f10d1b
17 changed files with 351 additions and 78 deletions

37
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,37 @@
<!-- If you have any issue with The Fuck, sorry about that, but we will do what we
can to fix that. Actually, maybe we already have, so first thing to do is to
update The Fuck and see if the bug is still there. -->
<!-- If it is (sorry again), check if the problem has not already been reported and
if not, just open an issue on [GitHub](https://github.com/nvbn/thefuck) with
the following basic information: -->
The output of `thefuck --version` (something like `The Fuck 3.1 using Python 3.5.0`):
FILL THIS IN
Your shell and its version (`bash`, `zsh`, *Windows PowerShell*, etc.):
FILL THIS IN
Your system (Debian 7, ArchLinux, Windows, etc.):
<!-- FILL THIS IN -->
How to reproduce the bug:
FILL THIS IN
The output of The Fuck with `THEFUCK_DEBUG=true` exported (typically execute `export THEFUCK_DEBUG=true` in your shell before The Fuck):
FILL THIS IN
If the bug only appears with a specific application, the output of that application and its version:
FILL THIS IN
Anything else you think is relevant:
<!-- FILL THIS IN -->
<!-- It's only with enough information that we can do something to fix the problem. -->

View File

@ -23,3 +23,37 @@ It's only with enough information that we can do something to fix the problem.
We gladly accept pull request on the [official
repository](https://github.com/nvbn/thefuck) for new rules, new features, bug
fixes, etc.
# Developing
Install `The Fuck` for development:
```bash
pip install -r requirements.txt
python setup.py develop
```
Run code style checks:
```bash
flake8
```
Run unit tests:
```bash
py.test
```
Run unit and functional tests (requires docker):
```bash
py.test --enable-functional
```
For sending package to pypi:
```bash
sudo apt-get install pandoc
./release.py
```

View File

@ -158,6 +158,7 @@ pip3 install thefuck --upgrade
The Fuck tries to match a rule for the previous command, creates a new command
using the matched rule and runs it. Rules enabled by default are as follows:
* `adb_unknown_command` &ndash; fixes misspelled commands like `adb logcta`;
* `ag_literal` &ndash; adds `-Q` to `ag` when suggested;
* `aws_cli` &ndash; fixes misspelled commands like `aws dynamdb scan`;
* `cargo` &ndash; runs `cargo build` instead of `cargo`;
@ -186,6 +187,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `git_branch_exists` &ndash; offers `git branch -d foo`, `git branch -D foo` or `git checkout foo` when creating a branch that already exists;
* `git_branch_list` &ndash; catches `git branch list` in place of `git branch` and removes created branch;
* `git_checkout` &ndash; fixes branch name or creates new branch;
* `git_commit_amend` &ndash; offers `git commit --amend` after previous commit;
* `git_diff_no_index` &ndash; adds `--no-index` to previous `git diff` on untracked files;
* `git_diff_staged` &ndash; adds `--staged` to previous `git diff` with unexpected output;
* `git_fix_stash` &ndash; fixes `git stash` commands (misspelled subcommand and missing `save`);
@ -283,6 +285,7 @@ Enabled by default only on specific platforms:
* `apt_get_search` &ndash; changes trying to search using `apt-get` with searching using `apt-cache`;
* `apt_invalid_operation` &ndash; fixes invalid `apt` and `apt-get` calls, like `apt-get isntall vim`;
* `apt_list_upgradable` &ndash; helps you run `apt list --upgradable` after `apt update`;
* `apt_upgrade` &ndash; helps you run `apt upgrade` after `apt list --upgradable`;
* `brew_cask_dependency` &ndash; installs cask dependencies;
* `brew_install` &ndash; fixes formula name for `brew install`;
* `brew_link` &ndash; adds `--overwrite --dry-run` if linking fails;
@ -444,37 +447,7 @@ eval $(thefuck --alias --enable-experimental-instant-mode)
## Developing
Install `The Fuck` for development:
```bash
pip install -r requirements.txt
python setup.py develop
```
Run code style checks:
```bash
flake8
```
Run unit tests:
```bash
py.test
```
Run unit and functional tests (requires docker):
```bash
py.test --enable-functional
```
For sending package to pypi:
```bash
sudo apt-get install pandoc
./release.py
```
See [CONTRIBUTING.md](CONTRIBUTING.md)
## License MIT
Project License can be found [here](LICENSE.md).

View File

@ -0,0 +1,41 @@
import pytest
from thefuck.rules.adb_unknown_command import match, get_new_command
from thefuck.types import Command
@pytest.fixture
def output():
return '''Android Debug Bridge version 1.0.31
-d - directs command to the only connected USB device
returns an error if more than one USB device is present.
-e - directs command to the only running emulator.
returns an error if more than one emulator is running.
-s <specific device> - directs command to the device or emulator with the given
serial number or qualifier. Overrides ANDROID_SERIAL
environment variable.
'''
@pytest.mark.parametrize('script', [
('adb lgcat'),
('adb puhs')])
def test_match(output, script):
assert match(Command(script, output))
@pytest.mark.parametrize('script', [
'git branch foo',
'abd push'])
def test_not_match(script):
assert not match(Command(script, ''))
@pytest.mark.parametrize('script, new_command', [
('adb puhs test.bin /sdcard/test.bin', 'adb push test.bin /sdcard/test.bin'),
('adb -s 1111 logcta', 'adb -s 1111 logcat'),
('adb -P 666 pulll /sdcard/test.bin', 'adb -P 666 pull /sdcard/test.bin'),
('adb -d logcatt', 'adb -d logcat'),
('adb -e reboott', 'adb -e reboot')])
def test_get_new_command(script, output, new_command):
assert get_new_command(Command(script, output)) == new_command

View File

@ -69,4 +69,7 @@ def test_not_match(command):
def test_get_new_command():
new_command = get_new_command(Command('sudo apt update', match_output))
assert new_command == 'sudo apt list --upgradable'
new_command = get_new_command(Command('apt update', match_output))
assert new_command == 'apt list --upgradable'

View File

@ -0,0 +1,36 @@
import pytest
from thefuck.rules.apt_upgrade import get_new_command, match
from thefuck.types import Command
match_output = '''
Listing... Done
heroku/stable 6.15.2-1 amd64 [upgradable from: 6.14.43-1]
resolvconf/zesty-updates,zesty-updates 1.79ubuntu4.1 all [upgradable from: 1.79ubuntu4]
squashfs-tools/zesty-updates 1:4.3-3ubuntu2.17.04.1 amd64 [upgradable from: 1:4.3-3ubuntu2]
unattended-upgrades/zesty-updates,zesty-updates 0.93.1ubuntu2.4 all [upgradable from: 0.93.1ubuntu2.3]
'''
no_match_output = '''
Listing... Done
'''
def test_match():
assert match(Command('apt list --upgradable', match_output))
assert match(Command('sudo apt list --upgradable', match_output))
@pytest.mark.parametrize('command', [
Command('apt list --upgradable', no_match_output),
Command('sudo apt list --upgradable', no_match_output)
])
def test_not_match(command):
assert not match(command)
def test_get_new_command():
new_command = get_new_command(Command('apt list --upgradable', match_output))
assert new_command == 'apt upgrade'
new_command = get_new_command(Command('sudo apt list --upgradable', match_output))
assert new_command == 'sudo apt upgrade'

View File

@ -4,8 +4,8 @@ from thefuck.types import Command
@pytest.fixture
def output(branch_name):
return "fatal: A branch named '{}' already exists.".format(branch_name)
def output(src_branch_name):
return "fatal: A branch named '{}' already exists.".format(src_branch_name)
@pytest.fixture
@ -17,18 +17,25 @@ def new_command(branch_name):
'git branch -D {0} && git checkout -b {0}', 'git checkout {0}']]
@pytest.mark.parametrize('script, branch_name', [
('git branch foo', 'foo'), ('git checkout bar', 'bar')])
@pytest.mark.parametrize('script, src_branch_name, branch_name', [
('git branch foo', 'foo', 'foo'),
('git checkout bar', 'bar', 'bar'),
('git checkout -b "let\'s-push-this"', '"let\'s-push-this"', '"let\'s-push-this"')])
def test_match(output, script, branch_name):
assert match(Command(script, output))
@pytest.mark.parametrize('script', ['git branch foo', 'git checkout bar'])
@pytest.mark.parametrize('script', [
'git branch foo',
'git checkout bar',
'git checkout -b "let\'s-push-this"'])
def test_not_match(script):
assert not match(Command(script, ''))
@pytest.mark.parametrize('script, branch_name, ', [
('git branch foo', 'foo'), ('git checkout bar', 'bar')])
def test_get_new_command(output, new_command, script, branch_name):
@pytest.mark.parametrize('script, src_branch_name, branch_name', [
('git branch foo', 'foo', 'foo'),
('git checkout bar', 'bar', 'bar'),
('git checkout -b "let\'s-push-this"', "let's-push-this", "let\\'s-push-this")])
def test_get_new_command(output, new_command, script, src_branch_name, branch_name):
assert get_new_command(Command(script, output)) == new_command

View File

@ -0,0 +1,25 @@
import pytest
from thefuck.rules.git_commit_amend import match, get_new_command
from thefuck.types import Command
@pytest.mark.parametrize('script, output', [
('git commit -m "test"', 'test output'),
('git commit', '')])
def test_match(output, script):
assert match(Command(script, output))
@pytest.mark.parametrize('script', [
'git branch foo',
'git checkout feature/test_commit',
'git push'])
def test_not_match(script):
assert not match(Command(script, ''))
@pytest.mark.parametrize('script', [
('git commit -m "test commit"'),
('git commit')])
def test_get_new_command(script):
assert get_new_command(Command(script, '')) == 'git commit --amend'

View File

@ -4,13 +4,15 @@ from thefuck.types import Command
@pytest.fixture
def output():
return '''fatal: The current branch master has no upstream branch.
def output(branch_name):
if not branch_name:
return ''
return '''fatal: The current branch {} has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin master
git push --set-upstream origin {}
'''
'''.format(branch_name, branch_name)
@pytest.fixture
@ -26,33 +28,44 @@ Branch feature/set-upstream set up to track remote branch feature/set-upstream f
'''
def test_match(output):
assert match(Command('git push', output))
assert match(Command('git push master', output))
assert not match(Command('git push master', ''))
assert not match(Command('ls', output))
@pytest.mark.parametrize('script, branch_name', [
('git push', 'master'),
('git push origin', 'master')])
def test_match(output, script, branch_name):
assert match(Command(script, output))
def test_match_bitbucket(output_bitbucket):
assert not match(Command('git push origin', output_bitbucket))
def test_get_new_command(output):
assert get_new_command(Command('git push', output))\
== "git push --set-upstream origin master"
assert get_new_command(Command('git push master', output))\
== "git push --set-upstream origin master"
assert get_new_command(Command('git push -u', output))\
== "git push --set-upstream origin master"
assert get_new_command(Command('git push -u origin', output))\
== "git push --set-upstream origin master"
assert get_new_command(Command('git push origin', output))\
== "git push --set-upstream origin master"
assert get_new_command(Command('git push --set-upstream origin', output))\
== "git push --set-upstream origin master"
assert get_new_command(Command('git push --quiet', output))\
== "git push --set-upstream origin master --quiet"
assert get_new_command(Command('git push --quiet origin', output))\
== "git push --set-upstream origin master --quiet"
assert get_new_command(Command('git -c test=test push --quiet origin', output))\
== "git -c test=test push --set-upstream origin master --quiet"
@pytest.mark.parametrize('script, branch_name', [
('git push master', None),
('ls', 'master')])
def test_not_match(output, script, branch_name):
assert not match(Command(script, output))
@pytest.mark.parametrize('script, branch_name, new_command', [
('git push', 'master',
'git push --set-upstream origin master'),
('git push master', 'master',
'git push --set-upstream origin master'),
('git push -u', 'master',
'git push --set-upstream origin master'),
('git push -u origin', 'master',
'git push --set-upstream origin master'),
('git push origin', 'master',
'git push --set-upstream origin master'),
('git push --set-upstream origin', 'master',
'git push --set-upstream origin master'),
('git push --quiet', 'master',
'git push --set-upstream origin master --quiet'),
('git push --quiet origin', 'master',
'git push --set-upstream origin master --quiet'),
('git -c test=test push --quiet origin', 'master',
'git -c test=test push --set-upstream origin master --quiet'),
('git push', "test's",
"git push --set-upstream origin test\\'s")])
def test_get_new_command(output, script, branch_name, new_command):
assert get_new_command(Command(script, output)) == new_command

View File

@ -13,9 +13,10 @@ class TestFish(object):
@pytest.fixture(autouse=True)
def Popen(self, mocker):
mock = mocker.patch('thefuck.shells.fish.Popen')
mock.return_value.stdout.read.return_value = (
mock.return_value.stdout.read.side_effect = [(
b'cd\nfish_config\nfuck\nfunced\nfuncsave\ngrep\nhistory\nll\nls\n'
b'man\nmath\npopd\npushd\nruby')
b'man\nmath\npopd\npushd\nruby'),
b'alias fish_key_reader /usr/bin/fish_key_reader\nalias g git']
return mock
@pytest.mark.parametrize('key, value', [
@ -42,7 +43,8 @@ class TestFish(object):
('open', 'open'),
('vim', 'vim'),
('ll', 'fish -ic "ll"'),
('ls', 'ls')]) # Fish has no aliases but functions
('ls', 'ls'),
('g', 'git')])
def test_from_shell(self, before, after, shell):
assert shell.from_shell(before) == after
@ -65,7 +67,9 @@ class TestFish(object):
'math': 'math',
'popd': 'popd',
'pushd': 'pushd',
'ruby': 'ruby'}
'ruby': 'ruby',
'g': 'git',
'fish_key_reader': '/usr/bin/fish_key_reader'}
def test_app_alias(self, shell):
assert 'function fuck' in shell.app_alias('fuck')

View File

@ -0,0 +1,54 @@
from thefuck.utils import is_app, get_closest, replace_argument
_ADB_COMMANDS = (
'backup',
'bugreport',
'connect',
'devices',
'disable-verity',
'disconnect',
'enable-verity',
'emu',
'forward',
'get-devpath',
'get-serialno',
'get-state',
'install',
'install-multiple',
'jdwp',
'keygen',
'kill-server',
'logcat',
'pull',
'push',
'reboot',
'reconnect',
'restore',
'reverse',
'root',
'run-as',
'shell',
'sideload',
'start-server',
'sync',
'tcpip',
'uninstall',
'unroot',
'usb',
'wait-for',
)
def match(command):
return (is_app(command, 'adb')
and command.output.startswith('Android Debug Bridge version'))
def get_new_command(command):
for idx, arg in enumerate(command.script_parts[1:]):
# allowed params to ADB are a/d/e/s/H/P/L where s, H, P and L take additional args
# for example 'adb -s 111 logcat' or 'adb -e logcat'
if not arg[0] == '-' and not command.script_parts[idx] in ('-s', '-H', '-P', '-L'):
adb_cmd = get_closest(arg, _ADB_COMMANDS)
return replace_argument(command.script, arg, adb_cmd)

View File

@ -11,5 +11,6 @@ def match(command):
return "Run 'apt list --upgradable' to see them." in command.output
@sudo_support
def get_new_command(command):
return 'apt list --upgradable'

View File

@ -0,0 +1,16 @@
from thefuck.specific.apt import apt_available
from thefuck.specific.sudo import sudo_support
from thefuck.utils import for_app
enabled_by_default = apt_available
@sudo_support
@for_app('apt')
def match(command):
return command.script == "apt list --upgradable" and len(command.output.strip().split('\n')) > 1
@sudo_support
def get_new_command(command):
return 'apt upgrade'

View File

@ -7,14 +7,15 @@ from thefuck.utils import eager
@git_support
def match(command):
return ("fatal: A branch named '" in command.output
and " already exists." in command.output)
and "' already exists." in command.output)
@git_support
@eager
def get_new_command(command):
branch_name = re.findall(
r"fatal: A branch named '([^']*)' already exists.", command.output)[0]
r"fatal: A branch named '(.+)' already exists.", command.output)[0]
branch_name = branch_name.replace("'", r"\'")
new_command_templates = [['git branch -d {0}', 'git branch {0}'],
['git branch -d {0}', 'git checkout -b {0}'],
['git branch -D {0}', 'git branch {0}'],

View File

@ -0,0 +1,11 @@
from thefuck.specific.git import git_support
@git_support
def match(command):
return ('commit' in command.script_parts)
@git_support
def get_new_command(command):
return 'git commit --amend'

View File

@ -5,7 +5,7 @@ from thefuck.specific.git import git_support
@git_support
def match(command):
return ('push' in command.script
return ('push' in command.script_parts
and 'git push --set-upstream' in command.output)
@ -39,6 +39,6 @@ def get_new_command(command):
while len(command_parts) > push_idx and command_parts[len(command_parts) - 1][0] != '-':
command_parts.pop(len(command_parts) - 1)
arguments = re.findall(r'git push (.*)', command.output)[0].strip()
arguments = re.findall(r'git push (.*)', command.output)[0].replace("'", r"\'").strip()
return replace_argument(" ".join(command_parts), 'push',
'push {}'.format(arguments))

View File

@ -10,12 +10,24 @@ from .generic import Generic
@cache('~/.config/fish/config.fish', '~/.config/fish/functions')
def _get_aliases(overridden):
def _get_functions(overridden):
proc = Popen(['fish', '-ic', 'functions'], stdout=PIPE, stderr=DEVNULL)
functions = proc.stdout.read().decode('utf-8').strip().split('\n')
return {func: func for func in functions if func not in overridden}
@cache('~/.config/fish/config.fish')
def _get_aliases(overridden):
aliases = {}
proc = Popen(['fish', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL)
alias_out = proc.stdout.read().decode('utf-8').strip().split('\n')
for alias in alias_out:
name, value = alias.replace('alias ', '', 1).split(' ', 1)
if name not in overridden:
aliases[name] = value
return aliases
class Fish(Generic):
def _get_overridden_aliases(self):
overridden = os.environ.get('THEFUCK_OVERRIDDEN_ALIASES',
@ -44,12 +56,17 @@ class Fish(Generic):
def get_aliases(self):
overridden = self._get_overridden_aliases()
return _get_aliases(overridden)
functions = _get_functions(overridden)
raw_aliases = _get_aliases(overridden)
functions.update(raw_aliases)
return functions
def _expand_aliases(self, command_script):
aliases = self.get_aliases()
binary = command_script.split(' ')[0]
if binary in aliases:
if binary in aliases and aliases[binary] != binary:
return command_script.replace(binary, aliases[binary], 1)
elif binary in aliases:
return u'fish -ic "{}"'.format(command_script.replace('"', r'\"'))
else:
return command_script