1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-02-22 12:58:33 +00:00

Merge branch 'master' of github.com:nvbn/thefuck

This commit is contained in:
Vladimir Iakovlev 2018-02-23 20:44:08 +01:00
commit a36a8b4de1
36 changed files with 527 additions and 88 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

@ -11,9 +11,6 @@ matrix:
- os: linux
dist: trusty
python: "3.4"
- os: linux
dist: trusty
python: "3.3"
- os: linux
dist: trusty
python: "2.7"

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

@ -1,7 +1,7 @@
The MIT License (MIT)
=====================
Copyright (c) 2015 Vladimir Iakovlev
Copyright (c) 2015-2018 Vladimir Iakovlev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -92,7 +92,7 @@ Reading package lists... Done
## Requirements
- python (3.3+)
- python (3.4+)
- pip
- python-dev
@ -111,6 +111,12 @@ sudo apt install python3-dev python3-pip
sudo pip3 install thefuck
```
On FreeBSD you can install `The Fuck` with:
```bash
sudo portsnap fetch update
cd /usr/ports/misc/thefuck && sudo make install clean
```
On other systems you can install `The Fuck` with `pip`:
```bash
@ -148,7 +154,7 @@ fuck -r
## Update
```bash
pip install thefuck --upgrade
pip3 install thefuck --upgrade
```
**Aliases changed in 1.34.**
@ -158,6 +164,7 @@ pip 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,11 +193,14 @@ 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`);
* `git_flag_after_filename` &ndash; fixes `fatal: bad flag '...' after filename`
* `git_help_aliased` &ndash; fixes `git help <alias>` commands replacing <alias> with the aliased command;
* `git_merge` &ndash; adds remote to branch names;
* `git_merge_unrelated` &ndash; adds `--allow-unrelated-histories` when required
* `git_not_command` &ndash; fixes wrong git commands like `git brnch`;
* `git_pull` &ndash; sets upstream before executing previous `git pull`;
* `git_pull_clone` &ndash; clones instead of pulling when the repo does not exist;
@ -268,6 +278,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `tsuru_not_command` &ndash; fixes wrong `tsuru` commands like `tsuru shell`;
* `tmux` &ndash; fixes `tmux` commands;
* `unknown_command` &ndash; fixes hadoop hdfs-style "unknown command", for example adds missing '-' to the command on `hdfs dfs ls`;
* `unsudo` &ndash; removes `sudo` from previous command if a process refuses to run on super user privilege.
* `vagrant_up` &ndash; starts up the vagrant instance;
* `whois` &ndash; fixes `whois` command;
* `workon_doesnt_exists` &ndash; fixes `virtualenvwrapper` env name os suggests to create new.
@ -282,6 +293,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;
@ -443,37 +455,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

@ -3,7 +3,6 @@ build: false
environment:
matrix:
- PYTHON: "C:/Python27"
- PYTHON: "C:/Python33"
- PYTHON: "C:/Python34"
- PYTHON: "C:/Python35"
- PYTHON: "C:/Python36"

View File

@ -26,8 +26,8 @@ if version < (2, 7):
print('thefuck requires Python version 2.7 or later' +
' ({}.{} detected).'.format(*version))
sys.exit(-1)
elif (3, 0) < version < (3, 3):
print('thefuck requires Python version 3.3 or later' +
elif (3, 0) < version < (3, 4):
print('thefuck requires Python version 3.4 or later' +
' ({}.{} detected).'.format(*version))
sys.exit(-1)

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

@ -52,7 +52,7 @@ def test_get_branches(branches, branch_list, git_branch):
@pytest.mark.parametrize('branches, command, new_command', [
(b'',
Command('git checkout unknown', did_not_match('unknown')),
'git branch unknown && git checkout unknown'),
'git checkout -b unknown'),
(b'',
Command('git commit unknown', did_not_match('unknown')),
'git branch unknown && git commit unknown'),

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

@ -0,0 +1,26 @@
import pytest
from thefuck.rules.git_merge import match, get_new_command
from thefuck.types import Command
@pytest.fixture
def output():
return 'merge: local - not something we can merge\n\n' \
'Did you mean this?\n\tremote/local'
def test_match(output):
assert match(Command('git merge test', output))
assert not match(Command('git merge master', ''))
assert not match(Command('ls', output))
@pytest.mark.parametrize('command, new_command', [
(Command('git merge local', output()),
'git merge remote/local'),
(Command('git merge -m "test" local', output()),
'git merge -m "test" remote/local'),
(Command('git merge -m "test local" local', output()),
'git merge -m "test local" remote/local')])
def test_get_new_command(command, new_command):
assert get_new_command(command) == new_command

View File

@ -0,0 +1,25 @@
import pytest
from thefuck.rules.git_merge_unrelated import match, get_new_command
from thefuck.types import Command
@pytest.fixture
def output():
return 'fatal: refusing to merge unrelated histories'
def test_match(output):
assert match(Command('git merge test', output))
assert not match(Command('git merge master', ''))
assert not match(Command('ls', output))
@pytest.mark.parametrize('command, new_command', [
(Command('git merge local', output()),
'git merge local --allow-unrelated-histories'),
(Command('git merge -m "test" local', output()),
'git merge -m "test" local --allow-unrelated-histories'),
(Command('git merge -m "test local" local', output()),
'git merge -m "test local" local --allow-unrelated-histories')])
def test_get_new_command(command, new_command):
assert get_new_command(command) == new_command

View File

@ -4,30 +4,68 @@ 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
def output_bitbucket():
return '''Total 0 (delta 0), reused 0 (delta 0)
remote:
remote: Create pull request for feature/set-upstream:
remote: https://bitbucket.org/set-upstream
remote:
To git@bitbucket.org:test.git
e5e7fbb..700d998 feature/set-upstream -> feature/set-upstream
Branch feature/set-upstream set up to track remote branch feature/set-upstream from origin.
'''
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_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 -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 --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"
def test_match_bitbucket(output_bitbucket):
assert not match(Command('git push origin', output_bitbucket))
@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

@ -7,7 +7,9 @@ from thefuck.types import Command
@pytest.mark.parametrize('command', [
Command(u'фзе-пуе', 'command not found: фзе-пуе'),
Command(u'λσ', 'command not found: λσ')])
Command(u'λσ', 'command not found: λσ'),
Command(u'שפא-עקא', 'command not found: שפא-עקא'),
Command(u'ךד', 'command not found: ךד')])
def test_match(command):
assert switch_lang.match(command)
@ -16,13 +18,16 @@ def test_match(command):
Command(u'pat-get', 'command not found: pat-get'),
Command(u'ls', 'command not found: ls'),
Command(u'агсл', 'command not found: агсл'),
Command(u'фзе-пуе', 'some info')])
Command(u'фзе-пуе', 'some info'),
Command(u'שפא-עקא', 'some info')])
def test_not_match(command):
assert not switch_lang.match(command)
@pytest.mark.parametrize('command, new_command', [
(Command(u'фзе-пуе штыефдд мшь', ''), 'apt-get install vim'),
(Command(u'λσ -λα', ''), 'ls -la')])
(Command(u'λσ -λα', ''), 'ls -la'),
(Command(u'שפא-עקא ןמדאשךך הןצ', ''), 'apt-get install vim'),
(Command(u'ךד -ךש', ''), 'ls -la')])
def test_get_new_command(command, new_command):
assert switch_lang.get_new_command(command) == new_command

View File

@ -0,0 +1,22 @@
import pytest
from thefuck.rules.unsudo import match, get_new_command
from thefuck.types import Command
@pytest.mark.parametrize('output', [
'you cannot perform this operation as root'])
def test_match(output):
assert match(Command('sudo ls', output))
def test_not_match():
assert not match(Command('', ''))
assert not match(Command('sudo ls', 'Permission denied'))
assert not match(Command('ls', 'you cannot perform this operation as root'))
@pytest.mark.parametrize('before, after', [
('sudo ls', 'ls'),
('sudo pacaur -S helloworld', 'pacaur -S helloworld')])
def test_get_new_command(before, after):
assert get_new_command(Command(before, '')) == after

View File

@ -51,6 +51,7 @@ class TestBash(object):
def test_app_alias_variables_correctly_set(self, shell):
alias = shell.app_alias('fuck')
assert "fuck () {" in alias
assert 'TF_SHELL=bash' in alias
assert "TF_ALIAS=fuck" in alias
assert 'PYTHONIOENCODING=utf-8' in alias
assert 'TF_SHELL_ALIASES=$(alias)' in alias

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,12 +67,15 @@ 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')
assert 'function FUCK' in shell.app_alias('FUCK')
assert 'thefuck' in shell.app_alias('fuck')
assert 'TF_SHELL=fish' in shell.app_alias('fuck')
assert 'TF_ALIAS=fuck PYTHONIOENCODING' in shell.app_alias('fuck')
assert 'PYTHONIOENCODING=utf-8 thefuck' in shell.app_alias('fuck')

View File

@ -44,6 +44,7 @@ class TestTcsh(object):
'll': 'ls -alF'}
def test_app_alias(self, shell):
assert 'setenv TF_SHELL tcsh' in shell.app_alias('fuck')
assert 'alias fuck' in shell.app_alias('fuck')
assert 'alias FUCK' in shell.app_alias('FUCK')
assert 'thefuck' in shell.app_alias('fuck')

View File

@ -51,6 +51,7 @@ class TestZsh(object):
def test_app_alias_variables_correctly_set(self, shell):
alias = shell.app_alias('fuck')
assert "fuck () {" in alias
assert 'TF_SHELL=zsh' in alias
assert "TF_ALIAS=fuck" in alias
assert 'PYTHONIOENCODING=utf-8' in alias
assert 'TF_SHELL_ALIASES=$(alias)' in alias

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

@ -34,6 +34,8 @@ def get_new_command(command):
fallback_to_first=False)
if closest_branch:
return replace_argument(command.script, missing_file, closest_branch)
elif command.script_parts[1] == 'checkout':
return replace_argument(command.script, 'checkout', 'checkout -b')
else:
return shell.and_('git branch {}', '{}').format(
missing_file, command.script)

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

@ -0,0 +1,18 @@
import re
from thefuck.utils import replace_argument
from thefuck.specific.git import git_support
@git_support
def match(command):
return ('merge' in command.script
and ' - not something we can merge' in command.output
and 'Did you mean this?' in command.output)
@git_support
def get_new_command(command):
unknown_branch = re.findall(r'merge: (.+) - not something we can merge', command.output)[0]
remote_branch = re.findall(r'Did you mean this\?\n\t([^\n]+)', command.output)[0]
return replace_argument(command.script, unknown_branch, remote_branch)

View File

@ -0,0 +1,12 @@
from thefuck.specific.git import git_support
@git_support
def match(command):
return ('merge' in command.script
and 'fatal: refusing to merge unrelated histories' in command.output)
@git_support
def get_new_command(command):
return command.script + ' --allow-unrelated-histories'

View File

@ -5,8 +5,8 @@ from thefuck.specific.git import git_support
@git_support
def match(command):
return ('push' in command.script
and 'set-upstream' in command.output)
return ('push' in command.script_parts
and 'git push --set-upstream' in command.output)
def _get_upstream_option_index(command_parts):
@ -32,7 +32,13 @@ def get_new_command(command):
# In case of `git push -u` we don't have next argument:
if len(command_parts) > upstream_option_index:
command_parts.pop(upstream_option_index)
else:
# the only non-qualified permitted options are the repository and refspec; git's
# suggestion include them, so they won't be lost, but would be duplicated otherwise.
push_idx = command_parts.index('push') + 1
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

@ -5,7 +5,8 @@ target_layout = '''qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"ZXCVB
source_layouts = [u'''йцукенгшщзхъфывапролджэячсмитьбю.ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,''',
u'''ضصثقفغعهخحجچشسیبلاتنمکگظطزرذدپو./ًٌٍَُِّْ][}{ؤئيإأآة»«:؛كٓژٰ‌ٔء><؟''',
u''';ςερτυθιοπ[]ασδφγηξκλ΄ζχψωβνμ,./:΅ΕΡΤΥΘΙΟΠ{}ΑΣΔΦΓΗΞΚΛ¨"ΖΧΨΩΒΝΜ<>?''']
u''';ςερτυθιοπ[]ασδφγηξκλ΄ζχψωβνμ,./:΅ΕΡΤΥΘΙΟΠ{}ΑΣΔΦΓΗΞΚΛ¨"ΖΧΨΩΒΝΜ<>?''',
u'''/'קראטוןםפ][שדגכעיחלךף,זסבהנמצתץ.QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?''']
@memoize

15
thefuck/rules/unsudo.py Normal file
View File

@ -0,0 +1,15 @@
patterns = ['you cannot perform this operation as root']
def match(command):
if command.script_parts and command.script_parts[0] != 'sudo':
return False
for pattern in patterns:
if pattern in command.output.lower():
return True
return False
def get_new_command(command):
return ' '.join(command.script_parts[1:])

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',
@ -35,7 +47,7 @@ class Fish(Generic):
# It is VERY important to have the variables declared WITHIN the alias
return ('function {0} -d "Correct your previous console command"\n'
' set -l fucked_up_command $history[1]\n'
' env TF_ALIAS={0} PYTHONIOENCODING=utf-8'
' env TF_SHELL=fish TF_ALIAS={0} PYTHONIOENCODING=utf-8'
' thefuck $fucked_up_command | read -l unfucked_command\n'
' if [ "$unfucked_command" != "" ]\n'
' eval $unfucked_command\n{1}'
@ -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

View File

@ -7,7 +7,7 @@ from .generic import Generic
class Tcsh(Generic):
def app_alias(self, alias_name):
return ("alias {0} 'setenv TF_ALIAS {0} && "
return ("alias {0} 'setenv TF_SHELL tcsh && setenv TF_ALIAS {0} && "
"set fucked_cmd=`history -h 2 | head -n 1` && "
"eval `thefuck ${{fucked_cmd}}`'").format(alias_name)

View File

@ -1,5 +1,5 @@
[tox]
envlist = py27,py33,py34,py35,py36
envlist = py27,py34,py35,py36
[testenv]
deps = -rrequirements.txt