1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-11-02 08:02:04 +00:00

Compare commits

...

13 Commits
1.49.1 ... 2.0

Author SHA1 Message Date
nvbn
4fb990742d Bump to 2.0 2015-07-19 22:33:56 +03:00
nvbn
cf3dca6f51 #284 Add coveralls support 2015-07-19 21:57:19 +03:00
nvbn
5187bada1b #N/A Update readme 2015-07-19 21:53:08 +03:00
nvbn
0238569b71 #N/A Require confirmation by default 2015-07-19 21:52:46 +03:00
nvbn
463b4fef2f Merge branch 'mcarton-git-aliases' 2015-07-19 21:29:39 +03:00
nvbn
f90bac10ed #290: Fix typo 2015-07-19 21:29:28 +03:00
nvbn
90014b2b05 Merge branch 'git-aliases' of https://github.com/mcarton/thefuck into mcarton-git-aliases 2015-07-19 21:27:04 +03:00
Vladimir Iakovlev
4276cacaf6 Merge pull request #292 from SimenB/delete-git-branch
Add git_branch_delete rule
2015-07-19 21:26:39 +03:00
Simen Bekkhus
b31aea3737 Add git_branch_delete rule 2015-07-19 13:45:46 +02:00
mcarton
5d0912fee8 Unquote over-quoted commands in @git_support
This allows writing rules more easily (eg. the git_branch_list rule
tests for `command.script.split() == 'git branch list'.split()`) and
looks nicer when `require_confirmation` is set.
2015-07-17 14:07:17 +02:00
mcarton
f6a4902074 Use @git_support in all git_* rules 2015-07-17 13:11:36 +02:00
mcarton
707d91200e Make the environment a setting
This would allow other rules to set the environment as needed for
`@git_support` and `GIT_TRACE`.
2015-07-17 11:37:13 +02:00
mcarton
b3e09d68df Start support for git aliases 2015-07-16 20:23:31 +02:00
22 changed files with 135 additions and 45 deletions

View File

@@ -6,4 +6,8 @@ python:
install: install:
- pip install -r requirements.txt - pip install -r requirements.txt
- python setup.py develop - python setup.py develop
script: py.test -v - pip install coveralls
script:
- export COVERAGE_PYTHON_VERSION=python-${TRAVIS_PYTHON_VERSION:0:1}
- coverage run --source=thefuck,tests -m py.test -v
after_success: coveralls

View File

@@ -1,4 +1,4 @@
# The Fuck [![Build Status](https://travis-ci.org/nvbn/thefuck.svg)](https://travis-ci.org/nvbn/thefuck) # The Fuck [![Build Status](https://travis-ci.org/nvbn/thefuck.svg)](https://travis-ci.org/nvbn/thefuck) [![Coverage Status](https://coveralls.io/repos/nvbn/thefuck/badge.svg?branch=master&service=github)](https://coveralls.io/github/nvbn/thefuck?branch=master)
**Aliases changed in 1.34.** **Aliases changed in 1.34.**
@@ -6,7 +6,9 @@ Magnificent app which corrects your previous console command,
inspired by a [@liamosaur](https://twitter.com/liamosaur/) inspired by a [@liamosaur](https://twitter.com/liamosaur/)
[tweet](https://twitter.com/liamosaur/status/506975850596536320). [tweet](https://twitter.com/liamosaur/status/506975850596536320).
Few examples: ![gif with examples](https://raw.githubusercontent.com/nvbn/thefuck/master/example.gif)
Few more examples:
```bash ```bash
➜ apt-get install vim ➜ apt-get install vim
@@ -159,6 +161,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `dry` – fix repetitions like "git git push"; * `dry` – fix repetitions like "git git push";
* `fix_alt_space` – replaces Alt+Space with Space character; * `fix_alt_space` – replaces Alt+Space with Space character;
* `git_add` – fix *"Did you forget to 'git add'?"*; * `git_add` – fix *"Did you forget to 'git add'?"*;
* `git_branch_delete` – changes `git branch -d` to `git branch -D`;
* `git_branch_list` – catches `git branch list` in place of `git branch` and removes created branch; * `git_branch_list` – catches `git branch list` in place of `git branch` and removes created branch;
* `git_checkout` – creates the branch before checking-out; * `git_checkout` – creates the branch before checking-out;
* `git_diff_staged` – adds `--staged` to previous `git diff` with unexpected output; * `git_diff_staged` – adds `--staged` to previous `git diff` with unexpected output;
@@ -250,7 +253,7 @@ priority = 1000 # Lower first
The Fuck has a few settings parameters which can be changed in `~/.thefuck/settings.py`: The Fuck has a few settings parameters which can be changed in `~/.thefuck/settings.py`:
* `rules` – list of enabled rules, by default `thefuck.conf.DEFAULT_RULES`; * `rules` – list of enabled rules, by default `thefuck.conf.DEFAULT_RULES`;
* `require_confirmation` – requires confirmation before running new command, by default `False`; * `require_confirmation` – requires confirmation before running new command, by default `True`;
* `wait_command` – max amount of time in seconds for getting previous command output; * `wait_command` – max amount of time in seconds for getting previous command output;
* `no_colors` – disable colored output; * `no_colors` – disable colored output;
* `priority` – dict with rules priorities, rule with lower `priority` will be matched first; * `priority` – dict with rules priorities, rule with lower `priority` will be matched first;

BIN
example.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 KiB

View File

@@ -11,7 +11,7 @@ elif (3, 0) < sys.version_info < (3, 3):
' ({}.{} detected).'.format(*sys.version_info[:2])) ' ({}.{} detected).'.format(*sys.version_info[:2]))
sys.exit(-1) sys.exit(-1)
VERSION = '1.49.1' VERSION = '2.0'
install_requires = ['psutil', 'colorama', 'six'] install_requires = ['psutil', 'colorama', 'six']
extras_require = {':python_version<"3.4"': ['pathlib']} extras_require = {':python_version<"3.4"': ['pathlib']}

View File

@@ -0,0 +1,22 @@
import pytest
from thefuck.rules.git_branch_delete import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr():
return '''error: The branch 'branch' is not fully merged.
If you are sure you want to delete it, run 'git branch -D branch'.
'''
def test_match(stderr):
assert match(Command('git branch -d branch', stderr=stderr), None)
assert not match(Command('git branch -d branch'), None)
assert not match(Command('ls', stderr=stderr), None)
def test_get_new_command(stderr):
assert get_new_command(Command('git branch -d branch', stderr=stderr), None)\
== "git branch -D branch"

View File

@@ -3,15 +3,13 @@ from thefuck.rules.git_diff_staged import match, get_new_command
from tests.utils import Command from tests.utils import Command
@pytest.mark.parametrize('command', [ @pytest.mark.parametrize('command', [Command(script='git diff')])
Command(script='git diff'),
Command(script='git df'),
Command(script='git ds')])
def test_match(command): def test_match(command):
assert match(command, None) assert match(command, None)
@pytest.mark.parametrize('command', [ @pytest.mark.parametrize('command', [
Command(script='git diff --staged'),
Command(script='git tag'), Command(script='git tag'),
Command(script='git branch'), Command(script='git branch'),
Command(script='git log')]) Command(script='git log')])
@@ -20,8 +18,6 @@ def test_not_match(command):
@pytest.mark.parametrize('command, new_command', [ @pytest.mark.parametrize('command, new_command', [
(Command('git diff'), 'git diff --staged'), (Command('git diff'), 'git diff --staged')])
(Command('git df'), 'git df --staged'),
(Command('git ds'), 'git ds --staged')])
def test_get_new_command(command, new_command): def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command assert get_new_command(command, None) == new_command

View File

@@ -3,22 +3,20 @@ from thefuck.rules.git_stash import match, get_new_command
from tests.utils import Command from tests.utils import Command
@pytest.fixture cherry_pick_error = (
def cherry_pick_error(): 'error: Your local changes would be overwritten by cherry-pick.\n'
return ('error: Your local changes would be overwritten by cherry-pick.\n' 'hint: Commit your changes or stash them to proceed.\n'
'hint: Commit your changes or stash them to proceed.\n' 'fatal: cherry-pick failed')
'fatal: cherry-pick failed')
@pytest.fixture rebase_error = (
def rebase_error(): 'Cannot rebase: Your index contains uncommitted changes.\n'
return ('Cannot rebase: Your index contains uncommitted changes.\n' 'Please commit or stash them.')
'Please commit or stash them.')
@pytest.mark.parametrize('command', [ @pytest.mark.parametrize('command', [
Command(script='git cherry-pick a1b2c3d', stderr=cherry_pick_error()), Command(script='git cherry-pick a1b2c3d', stderr=cherry_pick_error),
Command(script='git rebase -i HEAD~7', stderr=rebase_error())]) Command(script='git rebase -i HEAD~7', stderr=rebase_error)])
def test_match(command): def test_match(command):
assert match(command, None) assert match(command, None)

View File

@@ -77,23 +77,23 @@ class TestGetCommand(object):
monkeypatch.setattr('thefuck.shells.to_shell', lambda x: x) monkeypatch.setattr('thefuck.shells.to_shell', lambda x: x)
def test_get_command_calls(self, Popen): def test_get_command_calls(self, Popen):
assert main.get_command(Mock(), assert main.get_command(Mock(env={}),
['thefuck', 'apt-get', 'search', 'vim']) \ ['thefuck', 'apt-get', 'search', 'vim']) \
== Command('apt-get search vim', 'stdout', 'stderr') == Command('apt-get search vim', 'stdout', 'stderr')
Popen.assert_called_once_with('apt-get search vim', Popen.assert_called_once_with('apt-get search vim',
shell=True, shell=True,
stdout=PIPE, stdout=PIPE,
stderr=PIPE, stderr=PIPE,
env={'LANG': 'C'}) env={})
@pytest.mark.parametrize('args, result', [ @pytest.mark.parametrize('args, result', [
(['thefuck', 'ls', '-la'], 'ls -la'), (['thefuck', 'ls', '-la'], 'ls -la'),
(['thefuck', 'ls'], 'ls')]) (['thefuck', 'ls'], 'ls')])
def test_get_command_script(self, args, result): def test_get_command_script(self, args, result):
if result: if result:
assert main.get_command(Mock(), args).script == result assert main.get_command(Mock(env={}), args).script == result
else: else:
assert main.get_command(Mock(), args) is None assert main.get_command(Mock(env={}), args) is None
class TestGetMatchedRule(object): class TestGetMatchedRule(object):

View File

@@ -1,6 +1,6 @@
import pytest import pytest
from mock import Mock from mock import Mock
from thefuck.utils import sudo_support, wrap_settings, memoize, get_closest from thefuck.utils import git_support, sudo_support, wrap_settings, memoize, get_closest
from thefuck.types import Settings from thefuck.types import Settings
from tests.utils import Command from tests.utils import Command
@@ -26,6 +26,15 @@ def test_sudo_support(return_value, command, called, result):
fn.assert_called_once_with(Command(called), None) fn.assert_called_once_with(Command(called), None)
@pytest.mark.parametrize('called, command, stderr', [
('git co', 'git checkout', "19:22:36.299340 git.c:282 trace: alias expansion: co => 'checkout'"),
('git com file', 'git commit --verbose file', "19:23:25.470911 git.c:282 trace: alias expansion: com => 'commit' '--verbose'")])
def test_git_support(called, command, stderr):
@git_support
def fn(command, settings): return command.script
assert fn(Command(script=called, stderr=stderr), None) == command
def test_memoize(): def test_memoize():
fn = Mock(__name__='fn') fn = Mock(__name__='fn')
memoized = memoize(fn) memoized = memoize(fn)

View File

@@ -27,10 +27,11 @@ DEFAULT_PRIORITY = 1000
DEFAULT_SETTINGS = {'rules': DEFAULT_RULES, DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
'wait_command': 3, 'wait_command': 3,
'require_confirmation': False, 'require_confirmation': True,
'no_colors': False, 'no_colors': False,
'debug': False, 'debug': False,
'priority': {}} 'priority': {},
'env': {'LANG': 'C', 'GIT_TRACE': '1'}}
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules', ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
'THEFUCK_WAIT_COMMAND': 'wait_command', 'THEFUCK_WAIT_COMMAND': 'wait_command',

View File

@@ -81,8 +81,12 @@ def get_command(settings, args):
script = shells.from_shell(script) script = shells.from_shell(script)
logs.debug('Call: {}'.format(script), settings) logs.debug('Call: {}'.format(script), settings)
result = Popen(script, shell=True, stdout=PIPE, stderr=PIPE,
env=dict(os.environ, LANG='C')) env = dict(os.environ)
env.update(settings.env)
logs.debug('Executing with env: {}'.format(env), settings)
result = Popen(script, shell=True, stdout=PIPE, stderr=PIPE, env=env)
if wait_output(settings, result): if wait_output(settings, result):
return types.Command(script, result.stdout.read().decode('utf-8'), return types.Command(script, result.stdout.read().decode('utf-8'),
result.stderr.read().decode('utf-8')) result.stderr.read().decode('utf-8'))

View File

@@ -1,13 +1,15 @@
import re import re
from thefuck import shells from thefuck import utils, shells
@utils.git_support
def match(command, settings): def match(command, settings):
return ('git' in command.script return ('git' in command.script
and 'did not match any file(s) known to git.' in command.stderr and 'did not match any file(s) known to git.' in command.stderr
and "Did you forget to 'git add'?" in command.stderr) and "Did you forget to 'git add'?" in command.stderr)
@utils.git_support
def get_new_command(command, settings): def get_new_command(command, settings):
missing_file = re.findall( missing_file = re.findall(
r"error: pathspec '([^']*)' " r"error: pathspec '([^']*)' "

View File

@@ -0,0 +1,7 @@
def match(command, settings):
return ('git branch -d' in command.script
and 'If you are sure you want to delete it' in command.stderr)
def get_new_command(command, settings):
return command.script.replace('-d', '-D')

View File

@@ -1,10 +1,12 @@
from thefuck import shells from thefuck import utils, shells
@utils.git_support
def match(command, settings): def match(command, settings):
# catches "git branch list" in place of "git branch" # catches "git branch list" in place of "git branch"
return command.script.split() == 'git branch list'.split() return command.script.split() == 'git branch list'.split()
@utils.git_support
def get_new_command(command, settings): def get_new_command(command, settings):
return shells.and_('git branch --delete list', 'git branch') return shells.and_('git branch --delete list', 'git branch')

View File

@@ -1,13 +1,15 @@
import re import re
from thefuck import shells from thefuck import shells, utils
@utils.git_support
def match(command, settings): def match(command, settings):
return ('git' in command.script return ('git' in command.script
and 'did not match any file(s) known to git.' in command.stderr and 'did not match any file(s) known to git.' in command.stderr
and "Did you forget to 'git add'?" not in command.stderr) and "Did you forget to 'git add'?" not in command.stderr)
@utils.git_support
def get_new_command(command, settings): def get_new_command(command, settings):
missing_file = re.findall( missing_file = re.findall(
r"error: pathspec '([^']*)' " r"error: pathspec '([^']*)' "

View File

@@ -1,6 +1,13 @@
from thefuck import utils
@utils.git_support
def match(command, settings): def match(command, settings):
return command.script.startswith('git d') return ('git' in command.script and
'diff' in command.script and
'--staged' not in command.script)
@utils.git_support
def get_new_command(command, settings): def get_new_command(command, settings):
return '{} --staged'.format(command.script) return '{} --staged'.format(command.script)

View File

@@ -1,8 +1,8 @@
from difflib import get_close_matches
import re import re
from thefuck.utils import get_closest from thefuck.utils import get_closest, git_support
@git_support
def match(command, settings): def match(command, settings):
return ('git' in command.script return ('git' in command.script
and " is not a git command. See 'git --help'." in command.stderr and " is not a git command. See 'git --help'." in command.stderr
@@ -18,6 +18,7 @@ def _get_all_git_matched_commands(stderr):
yield line.strip() yield line.strip()
@git_support
def get_new_command(command, settings): def get_new_command(command, settings):
broken_cmd = re.findall(r"git: '([^']*)' is not a git command", broken_cmd = re.findall(r"git: '([^']*)' is not a git command",
command.stderr)[0] command.stderr)[0]

View File

@@ -1,12 +1,14 @@
from thefuck import shells from thefuck import shells, utils
@utils.git_support
def match(command, settings): def match(command, settings):
return ('git' in command.script return ('git' in command.script
and 'pull' in command.script and 'pull' in command.script
and 'set-upstream' in command.stderr) and 'set-upstream' in command.stderr)
@utils.git_support
def get_new_command(command, settings): def get_new_command(command, settings):
line = command.stderr.split('\n')[-3].strip() line = command.stderr.split('\n')[-3].strip()
branch = line.split(' ')[-1] branch = line.split(' ')[-1]

View File

@@ -1,8 +1,13 @@
from thefuck import utils
@utils.git_support
def match(command, settings): def match(command, settings):
return ('git' in command.script return ('git' in command.script
and 'push' in command.script and 'push' in command.script
and 'set-upstream' in command.stderr) and 'set-upstream' in command.stderr)
@utils.git_support
def get_new_command(command, settings): def get_new_command(command, settings):
return command.stderr.split('\n')[-3].strip() return command.stderr.split('\n')[-3].strip()

View File

@@ -1,12 +1,14 @@
from thefuck import shells from thefuck import shells, utils
@utils.git_support
def match(command, settings): def match(command, settings):
# catches "Please commit or stash them" and "Please, commit your changes or # catches "Please commit or stash them" and "Please, commit your changes or
# stash them before you can switch branches." # stash them before you can switch branches."
return 'git' in command.script and 'or stash them' in command.stderr return 'git' in command.script and 'or stash them' in command.stderr
@utils.git_support
def get_new_command(command, settings): def get_new_command(command, settings):
formatme = shells.and_('git stash', '{}') formatme = shells.and_('git stash', '{}')
return formatme.format(command.script) return formatme.format(command.script)

View File

@@ -9,7 +9,6 @@ from time import time
import os import os
import io import io
from psutil import Process from psutil import Process
import six
from .utils import DEVNULL, memoize from .utils import DEVNULL, memoize

View File

@@ -1,7 +1,9 @@
from difflib import get_close_matches from difflib import get_close_matches
from functools import wraps from functools import wraps
from shlex import split
import os import os
import pickle import pickle
import re
import six import six
from .types import Command from .types import Command
@@ -9,11 +11,9 @@ from .types import Command
DEVNULL = open(os.devnull, 'w') DEVNULL = open(os.devnull, 'w')
if six.PY2: if six.PY2:
import pipes from pipes import quote
quote = pipes.quote
else: else:
import shlex from shlex import quote
quote = shlex.quote
def which(program): def which(program):
@@ -73,6 +73,30 @@ def sudo_support(fn):
return wrapper return wrapper
def git_support(fn):
"""Resolve git aliases."""
@wraps(fn)
def wrapper(command, settings):
if (command.script.startswith('git') and
'trace: alias expansion:' in command.stderr):
search = re.search("trace: alias expansion: ([^ ]*) => ([^\n]*)",
command.stderr)
alias = search.group(1)
# by default git quotes everything, for example:
# 'commit' '--amend'
# which is surprising and does not allow to easily test for
# eg. 'git commit'
expansion = ' '.join(map(quote, split(search.group(2))))
new_script = command.script.replace(alias, expansion)
command = Command._replace(command, script=new_script)
return fn(command, settings)
return wrapper
def memoize(fn): def memoize(fn):
"""Caches previous calls to the function.""" """Caches previous calls to the function."""
memo = {} memo = {}