mirror of
				https://github.com/nvbn/thefuck.git
				synced 2025-11-03 16:42:03 +00:00 
			
		
		
		
	Compare commits
	
		
			34 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					d5cc7ec43b | ||
| 
						 | 
					33a87502cd | ||
| 
						 | 
					82a12dda81 | ||
| 
						 | 
					4c9099a79b | ||
| 
						 | 
					1508ecfeae | ||
| 
						 | 
					284d49da8d | ||
| 
						 | 
					fb39d0bbd3 | ||
| 
						 | 
					ed24e4ca61 | ||
| 
						 | 
					a36a8b4de1 | ||
| 
						 | 
					2678adf981 | ||
| 
						 | 
					dd9554539f | ||
| 
						 | 
					b65e3a9aad | ||
| 
						 | 
					027b41da59 | ||
| 
						 | 
					aa45585601 | ||
| 
						 | 
					c205683a8d | ||
| 
						 | 
					7c858fadb3 | ||
| 
						 | 
					797ca1c564 | ||
| 
						 | 
					7b10a86267 | ||
| 
						 | 
					b62bb90a0d | ||
| 
						 | 
					a696461cd3 | ||
| 
						 | 
					7e6d1dbc7c | ||
| 
						 | 
					4fb85b0a92 | ||
| 
						 | 
					83e1710712 | ||
| 
						 | 
					045c8ae76c | ||
| 
						 | 
					bcb749722b | ||
| 
						 | 
					f700b23f57 | ||
| 
						 | 
					897572d278 | ||
| 
						 | 
					0640509895 | ||
| 
						 | 
					57fb6e079a | ||
| 
						 | 
					83cf97dc26 | ||
| 
						 | 
					9e788196e6 | ||
| 
						 | 
					4ea02a3153 | ||
| 
						 | 
					f966ecd4f5 | ||
| 
						 | 
					629056077f | 
							
								
								
									
										37
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal 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. -->
 | 
			
		||||
							
								
								
									
										12
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								.travis.yml
									
									
									
									
									
								
							@@ -11,17 +11,10 @@ matrix:
 | 
			
		||||
    - os: linux
 | 
			
		||||
      dist: trusty
 | 
			
		||||
      python: "3.4"
 | 
			
		||||
    - os: linux
 | 
			
		||||
      dist: trusty
 | 
			
		||||
      python: "3.3"
 | 
			
		||||
    - os: linux
 | 
			
		||||
      dist: trusty
 | 
			
		||||
      python: "2.7"
 | 
			
		||||
    - os: osx
 | 
			
		||||
      env: FORMULA="python"
 | 
			
		||||
      language: generic
 | 
			
		||||
    - os: osx
 | 
			
		||||
      env: FORMULA="python3"
 | 
			
		||||
      language: generic
 | 
			
		||||
services:
 | 
			
		||||
  - docker
 | 
			
		||||
@@ -32,8 +25,9 @@ addons:
 | 
			
		||||
      - python3-commandnotfound
 | 
			
		||||
before_install:
 | 
			
		||||
  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew update ; fi
 | 
			
		||||
  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then if brew ls --versions $FORMULA; then brew upgrade $FORMULA || echo Python is up to date; else brew install $FORMULA; fi; fi
 | 
			
		||||
  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then virtualenv venv -p $FORMULA; fi
 | 
			
		||||
  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew upgrade python; fi
 | 
			
		||||
  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then pip3 install virtualenv; fi
 | 
			
		||||
  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then virtualenv venv -p python3; fi
 | 
			
		||||
  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then source venv/bin/activate; fi
 | 
			
		||||
  - pip install -U pip
 | 
			
		||||
  - pip install -U coveralls
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										142
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										142
									
								
								README.md
									
									
									
									
									
								
							@@ -1,14 +1,15 @@
 | 
			
		||||
# The Fuck [![Version][version-badge]][version-link] [![Build Status][travis-badge]][travis-link] [![Windows Build Status][appveyor-badge]][appveyor-link] [![Coverage][coverage-badge]][coverage-link] [![MIT License][license-badge]](LICENSE.md)
 | 
			
		||||
 | 
			
		||||
Magnificent app which corrects your previous console command,
 | 
			
		||||
inspired by a [@liamosaur](https://twitter.com/liamosaur/)
 | 
			
		||||
[tweet](https://twitter.com/liamosaur/status/506975850596536320).
 | 
			
		||||
*The Fuck* is a magnificent app, inspired by a [@liamosaur](https://twitter.com/liamosaur/)
 | 
			
		||||
[tweet](https://twitter.com/liamosaur/status/506975850596536320),
 | 
			
		||||
that corrects errors in previous console commands.
 | 
			
		||||
 | 
			
		||||
The Fuck is too slow? [Try experimental instant mode!](#experimental-instant-mode) 
 | 
			
		||||
 | 
			
		||||
Is *The Fuck* too slow? [Try the experimental instant mode!](#experimental-instant-mode)
 | 
			
		||||
 | 
			
		||||
[![gif with examples][examples-link]][examples-link]
 | 
			
		||||
 | 
			
		||||
Few more examples:
 | 
			
		||||
More examples:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
➜ apt-get install vim
 | 
			
		||||
@@ -75,8 +76,8 @@ REPL-y 0.3.1
 | 
			
		||||
...
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
If you are not scared to blindly run the changed command, there is a `require_confirmation`
 | 
			
		||||
[settings](#settings) option:
 | 
			
		||||
If you're not afraid of blindly running corrected commands, the
 | 
			
		||||
`require_confirmation` [settings](#settings) option can be disabled:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
➜ apt-get install vim
 | 
			
		||||
@@ -92,35 +93,42 @@ Reading package lists... Done
 | 
			
		||||
 | 
			
		||||
## Requirements
 | 
			
		||||
 | 
			
		||||
- python (3.3+)
 | 
			
		||||
- python (3.4+)
 | 
			
		||||
- pip
 | 
			
		||||
- python-dev
 | 
			
		||||
 | 
			
		||||
## Installation
 | 
			
		||||
 | 
			
		||||
On OS X you can install `The Fuck` with [Homebrew][homebrew]:
 | 
			
		||||
On OS X, you can install *The Fuck* via [Homebrew][homebrew]:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
brew install thefuck
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
On Ubuntu you can install `The Fuck` with:
 | 
			
		||||
On Ubuntu, install *The Fuck* with the following commands:
 | 
			
		||||
```bash
 | 
			
		||||
sudo apt update
 | 
			
		||||
sudo apt install python3-dev python3-pip
 | 
			
		||||
sudo pip3 install thefuck
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
On other systems you can install `The Fuck` with `pip`:
 | 
			
		||||
On FreeBSD, install *The Fuck* with the following commands:
 | 
			
		||||
```bash
 | 
			
		||||
sudo portsnap fetch update
 | 
			
		||||
cd /usr/ports/misc/thefuck && sudo make install clean
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
On other systems, install *The Fuck*  by using `pip`:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
pip install thefuck
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
[Or using an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation)
 | 
			
		||||
[Alternatively, you may use an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation)
 | 
			
		||||
 | 
			
		||||
<a href='#manual-installation' name='manual-installation'>#</a>
 | 
			
		||||
You should place this command in your `.bash_profile`, `.bashrc`, `.zshrc` or other startup script:
 | 
			
		||||
It is recommended that you place this command in your `.bash_profile`,
 | 
			
		||||
`.bashrc`, `.zshrc` or other startup script:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
eval $(thefuck --alias)
 | 
			
		||||
@@ -130,34 +138,36 @@ eval $(thefuck --alias FUCK)
 | 
			
		||||
 | 
			
		||||
[Or in your shell config (Bash, Zsh, Fish, Powershell, tcsh).](https://github.com/nvbn/thefuck/wiki/Shell-aliases)
 | 
			
		||||
 | 
			
		||||
Changes will be available only in a new shell session.
 | 
			
		||||
To make them available immediately, run `source ~/.bashrc` (or your shell config file like `.zshrc`).
 | 
			
		||||
Changes are only available in a new shell session. To make changes immediately
 | 
			
		||||
available, run `source ~/.bashrc` (or your shell config file like `.zshrc`).
 | 
			
		||||
 | 
			
		||||
If you want to run fixed command without confirmation you can use `-y` option:
 | 
			
		||||
To run fixed commands without confirmation, use the `-y` option:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
fuck -y
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
If you want to fix commands recursively until success you can use `-r` option:
 | 
			
		||||
To fix commands recursively until succeeding, use the `-r` option:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
fuck -r
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Update
 | 
			
		||||
## Updating
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
pip install thefuck --upgrade
 | 
			
		||||
pip3 install thefuck --upgrade
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**Aliases changed in 1.34.**
 | 
			
		||||
**Note: Alias functionality was changed in v1.34 of *The Fuck***
 | 
			
		||||
 | 
			
		||||
## How it works
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
*The Fuck* attempts to match the previous command with a rule. If a match is
 | 
			
		||||
found, a new command is created using the matched rule and executed. The
 | 
			
		||||
following rules are enabled by default:
 | 
			
		||||
 | 
			
		||||
* `adb_unknown_command` – fixes misspelled commands like `adb logcta`;
 | 
			
		||||
* `ag_literal` – adds `-Q` to `ag` when suggested;
 | 
			
		||||
* `aws_cli` – fixes misspelled commands like `aws dynamdb scan`;
 | 
			
		||||
* `cargo` – runs `cargo build` instead of `cargo`;
 | 
			
		||||
@@ -186,11 +196,14 @@ using the matched rule and runs it. Rules enabled by default are as follows:
 | 
			
		||||
* `git_branch_exists` – offers `git branch -d foo`, `git branch -D foo` or `git checkout foo` when creating a branch that already exists;
 | 
			
		||||
* `git_branch_list` – catches `git branch list` in place of `git branch` and removes created branch;
 | 
			
		||||
* `git_checkout` – fixes branch name or creates new branch;
 | 
			
		||||
* `git_commit_amend` – offers `git commit --amend` after previous commit;
 | 
			
		||||
* `git_diff_no_index` – adds `--no-index` to previous `git diff` on untracked files;
 | 
			
		||||
* `git_diff_staged` – adds `--staged` to previous `git diff` with unexpected output;
 | 
			
		||||
* `git_fix_stash` – fixes `git stash` commands (misspelled subcommand and missing `save`);
 | 
			
		||||
* `git_flag_after_filename` – fixes `fatal: bad flag '...' after filename`
 | 
			
		||||
* `git_help_aliased` – fixes `git help <alias>` commands replacing <alias> with the aliased command;
 | 
			
		||||
* `git_merge` – adds remote to branch names;
 | 
			
		||||
* `git_merge_unrelated` – adds `--allow-unrelated-histories` when required
 | 
			
		||||
* `git_not_command` – fixes wrong git commands like `git brnch`;
 | 
			
		||||
* `git_pull` – sets upstream before executing previous `git pull`;
 | 
			
		||||
* `git_pull_clone` – clones instead of pulling when the repo does not exist;
 | 
			
		||||
@@ -268,6 +281,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
 | 
			
		||||
* `tsuru_not_command` – fixes wrong `tsuru` commands like `tsuru shell`;
 | 
			
		||||
* `tmux` – fixes `tmux` commands;
 | 
			
		||||
* `unknown_command` – fixes hadoop hdfs-style "unknown command", for example adds missing '-' to the command on `hdfs dfs ls`;
 | 
			
		||||
* `unsudo` – removes `sudo` from previous command if a process refuses to run on super user privilege.
 | 
			
		||||
* `vagrant_up` – starts up the vagrant instance;
 | 
			
		||||
* `whois` – fixes `whois` command;
 | 
			
		||||
* `workon_doesnt_exists` – fixes `virtualenvwrapper` env name os suggests to create new.
 | 
			
		||||
@@ -276,12 +290,13 @@ using the matched rule and runs it. Rules enabled by default are as follows:
 | 
			
		||||
* `yarn_command_replaced` – fixes replaced `yarn` commands;
 | 
			
		||||
* `yarn_help` – makes it easier to open `yarn` documentation;
 | 
			
		||||
 | 
			
		||||
Enabled by default only on specific platforms:
 | 
			
		||||
The following rules are enabled by default on specific platforms only:
 | 
			
		||||
 | 
			
		||||
* `apt_get` – installs app from apt if it not installed (requires `python-commandnotfound` / `python3-commandnotfound`);
 | 
			
		||||
* `apt_get_search` – changes trying to search using `apt-get` with searching using `apt-cache`;
 | 
			
		||||
* `apt_invalid_operation` – fixes invalid `apt` and `apt-get` calls, like `apt-get isntall vim`;
 | 
			
		||||
* `apt_list_upgradable` – helps you run `apt list --upgradable` after `apt update`;
 | 
			
		||||
* `apt_upgrade` – helps you run `apt upgrade` after `apt list --upgradable`;
 | 
			
		||||
* `brew_cask_dependency` – installs cask dependencies;
 | 
			
		||||
* `brew_install` – fixes formula name for `brew install`;
 | 
			
		||||
* `brew_link` – adds `--overwrite --dry-run` if linking fails;
 | 
			
		||||
@@ -292,36 +307,40 @@ Enabled by default only on specific platforms:
 | 
			
		||||
* `pacman` – installs app with `pacman` if it is not installed (uses `yaourt` if available);
 | 
			
		||||
* `pacman_not_found` – fixes package name with `pacman` or `yaourt`.
 | 
			
		||||
 | 
			
		||||
Bundled, but not enabled by default:
 | 
			
		||||
The following commands are bundled with *The Fuck*, but are not enabled by
 | 
			
		||||
default:
 | 
			
		||||
 | 
			
		||||
* `git_push_force` – adds `--force-with-lease` to a `git push` (may conflict with `git_push_pull`);
 | 
			
		||||
* `rm_root` – adds `--no-preserve-root` to `rm -rf /` command.
 | 
			
		||||
 | 
			
		||||
## Creating your own rules
 | 
			
		||||
 | 
			
		||||
For adding your own rule you should create `your-rule-name.py`
 | 
			
		||||
in `~/.config/thefuck/rules`. The rule should contain two functions:
 | 
			
		||||
To add your own rule, create a file named `your-rule-name.py`
 | 
			
		||||
in `~/.config/thefuck/rules`. The rule file must contain two functions:
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
match(command: Command) -> bool
 | 
			
		||||
get_new_command(command: Command) -> str | list[str]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Also the rule can contain an optional function
 | 
			
		||||
Additionally, rules can contain optional functions:
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
side_effect(old_command: Command, fixed_command: str) -> None
 | 
			
		||||
```
 | 
			
		||||
and optional `enabled_by_default`, `requires_output` and `priority` variables.
 | 
			
		||||
Rules can also contain the optional variables `enabled_by_default`, `requires_output` and `priority`.
 | 
			
		||||
 | 
			
		||||
`Command` has three attributes: `script`, `output` and `script_parts`.
 | 
			
		||||
Rule shouldn't change `Command`.
 | 
			
		||||
Your rule should not change `Command`.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
*Rules api changed in 3.0:* For accessing settings in rule you need to import it with `from thefuck.conf import settings`.
 | 
			
		||||
`settings` is a special object filled with `~/.config/thefuck/settings.py` and values from env ([see more below](#settings)).
 | 
			
		||||
**Rules api changed in 3.0:** To access a rule's settings, import it with
 | 
			
		||||
 `from thefuck.conf import settings`
 | 
			
		||||
  
 | 
			
		||||
`settings` is a special object assembled from `~/.config/thefuck/settings.py`, 
 | 
			
		||||
and values from env ([see more below](#settings)).
 | 
			
		||||
 | 
			
		||||
Simple example of the rule for running script with `sudo`:
 | 
			
		||||
A simple example rule for running a script with `sudo`:
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
def match(command):
 | 
			
		||||
@@ -349,7 +368,8 @@ requires_output = True
 | 
			
		||||
 | 
			
		||||
## Settings
 | 
			
		||||
 | 
			
		||||
The Fuck has a few settings parameters which can be changed in `$XDG_CONFIG_HOME/thefuck/settings.py` (`$XDG_CONFIG_HOME` defaults to `~/.config`):
 | 
			
		||||
Several *The Fuck* parameters can be changed in the file `$XDG_CONFIG_HOME/thefuck/settings.py`
 | 
			
		||||
(`$XDG_CONFIG_HOME` defaults to `~/.config`):
 | 
			
		||||
 | 
			
		||||
* `rules` – list of enabled rules, by default `thefuck.conf.DEFAULT_RULES`;
 | 
			
		||||
* `exclude_rules` – list of disabled rules, by default `[]`;
 | 
			
		||||
@@ -363,7 +383,7 @@ The Fuck has a few settings parameters which can be changed in `$XDG_CONFIG_HOME
 | 
			
		||||
* `wait_slow_command` – max amount of time in seconds for getting previous command output if it in `slow_commands` list;
 | 
			
		||||
* `slow_commands` – list of slow commands.
 | 
			
		||||
 | 
			
		||||
Example of `settings.py`:
 | 
			
		||||
An example of `settings.py`:
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
rules = ['sudo', 'no_command']
 | 
			
		||||
@@ -407,9 +427,9 @@ export THEFUCK_HISTORY_LIMIT='2000'
 | 
			
		||||
 | 
			
		||||
## Third-party packages with rules
 | 
			
		||||
 | 
			
		||||
If you want to make very specific rules or rules, that you don't want to make public,
 | 
			
		||||
but share with other people.
 | 
			
		||||
You can create a special package with name `thefuck_contrib_*` with following structure:
 | 
			
		||||
If you'd like to make a specific set of non-public rules, but would still like
 | 
			
		||||
to share them with others, create a package named `thefuck_contrib_*` with
 | 
			
		||||
the following structure:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
thefuck_contrib_foo
 | 
			
		||||
@@ -422,20 +442,22 @@ thefuck_contrib_foo
 | 
			
		||||
  setup.py
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
And thefuck will find all rules from `rules` module.
 | 
			
		||||
*The Fuck* will find rules located in the `rules` module.
 | 
			
		||||
 | 
			
		||||
## Experimental instant mode
 | 
			
		||||
 | 
			
		||||
By default The Fuck reruns a previous command and that takes time,
 | 
			
		||||
in instant mode The Fuck logs output with [script](https://en.wikipedia.org/wiki/Script_(Unix))
 | 
			
		||||
and just reads the log.
 | 
			
		||||
The default behavior of *The Fuck* requires time to re-run previous commands.
 | 
			
		||||
When in instant mode, *The Fuck* saves time by logging output with [script](https://en.wikipedia.org/wiki/Script_(Unix)),
 | 
			
		||||
then reading the log.
 | 
			
		||||
 | 
			
		||||
[![gif with instant mode][instant-mode-gif-link]][instant-mode-gif-link]
 | 
			
		||||
 | 
			
		||||
At the moment only Python 3 with bash or zsh is supported.
 | 
			
		||||
Currently, instant mode only supports Python 3 with bash or zsh.
 | 
			
		||||
 | 
			
		||||
For enabling instant mode you need to add `--enable-experimental-instant-mode`
 | 
			
		||||
to alias initialization in your `.bashrc`, `.bash_profile` or `.zshrc` like:
 | 
			
		||||
To enable instant mode, add `--enable-experimental-instant-mode`
 | 
			
		||||
to the alias initialization in `.bashrc`, `.bash_profile` or `.zshrc`.
 | 
			
		||||
 | 
			
		||||
For example:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
eval $(thefuck --alias --enable-experimental-instant-mode)
 | 
			
		||||
@@ -443,37 +465,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).
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ build: false
 | 
			
		||||
environment:
 | 
			
		||||
  matrix:
 | 
			
		||||
    - PYTHON: "C:/Python27"
 | 
			
		||||
    - PYTHON: "C:/Python33"
 | 
			
		||||
    - PYTHON: "C:/Python34"
 | 
			
		||||
    - PYTHON: "C:/Python35"
 | 
			
		||||
    - PYTHON: "C:/Python36"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								setup.py
									
									
									
									
									
								
							@@ -26,12 +26,12 @@ 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)
 | 
			
		||||
 | 
			
		||||
VERSION = '3.25'
 | 
			
		||||
VERSION = '3.26'
 | 
			
		||||
 | 
			
		||||
install_requires = ['psutil', 'colorama', 'six', 'decorator', 'pyte']
 | 
			
		||||
extras_require = {':python_version<"3.4"': ['pathlib2'],
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										41
									
								
								tests/rules/test_adb_unknown_command.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								tests/rules/test_adb_unknown_command.py
									
									
									
									
									
										Normal 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
 | 
			
		||||
@@ -12,9 +12,8 @@ from thefuck.types import Command
 | 
			
		||||
     [('vim', 'main'), ('vim-tiny', 'main')])])
 | 
			
		||||
def test_match(mocker, command, packages):
 | 
			
		||||
    mocker.patch('thefuck.rules.apt_get.which', return_value=None)
 | 
			
		||||
    mock = mocker.patch('thefuck.rules.apt_get.command_not_found',
 | 
			
		||||
                        create=True)
 | 
			
		||||
    mock.getPackages.return_value = packages
 | 
			
		||||
    mocker.patch('thefuck.rules.apt_get._get_packages',
 | 
			
		||||
                 create=True, return_value=packages)
 | 
			
		||||
 | 
			
		||||
    assert match(command)
 | 
			
		||||
 | 
			
		||||
@@ -30,9 +29,8 @@ def test_match(mocker, command, packages):
 | 
			
		||||
     ['vim'], '/usr/bin/vim')])
 | 
			
		||||
def test_not_match(mocker, command, packages, which):
 | 
			
		||||
    mocker.patch('thefuck.rules.apt_get.which', return_value=which)
 | 
			
		||||
    mock = mocker.patch('thefuck.rules.apt_get.command_not_found',
 | 
			
		||||
                        create=True)
 | 
			
		||||
    mock.getPackages.return_value = packages
 | 
			
		||||
    mocker.patch('thefuck.rules.apt_get._get_packages',
 | 
			
		||||
                 create=True, return_value=packages)
 | 
			
		||||
 | 
			
		||||
    assert not match(command)
 | 
			
		||||
 | 
			
		||||
@@ -49,7 +47,7 @@ def test_not_match(mocker, command, packages, which):
 | 
			
		||||
     [('imagemagick', 'main'),
 | 
			
		||||
      ('graphicsmagick-imagemagick-compat', 'universe')])])
 | 
			
		||||
def test_get_new_command(mocker, command, new_command, packages):
 | 
			
		||||
    mock = mocker.patch('thefuck.rules.apt_get.command_not_found',
 | 
			
		||||
                        create=True)
 | 
			
		||||
    mock.getPackages.return_value = packages
 | 
			
		||||
    mocker.patch('thefuck.rules.apt_get._get_packages',
 | 
			
		||||
                 create=True, return_value=packages)
 | 
			
		||||
 | 
			
		||||
    assert get_new_command(command) == new_command
 | 
			
		||||
 
 | 
			
		||||
@@ -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'
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								tests/rules/test_apt_upgrade.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								tests/rules/test_apt_upgrade.py
									
									
									
									
									
										Normal 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'
 | 
			
		||||
							
								
								
									
										23
									
								
								tests/rules/test_cd_correction.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								tests/rules/test_cd_correction.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
import pytest
 | 
			
		||||
from thefuck.rules.cd_correction import match
 | 
			
		||||
from thefuck.types import Command
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('command', [
 | 
			
		||||
    Command('cd foo', 'cd: foo: No such file or directory'),
 | 
			
		||||
    Command('cd foo/bar/baz',
 | 
			
		||||
            'cd: foo: No such file or directory'),
 | 
			
		||||
    Command('cd foo/bar/baz', 'cd: can\'t cd to foo/bar/baz'),
 | 
			
		||||
    Command('cd /foo/bar/', 'cd: The directory "/foo/bar/" does not exist')])
 | 
			
		||||
def test_match(command):
 | 
			
		||||
    assert match(command)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('command', [
 | 
			
		||||
    Command('cd foo', ''), Command('', '')])
 | 
			
		||||
def test_not_match(command):
 | 
			
		||||
    assert not match(command)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Note that get_new_command uses local filesystem, so not testing it here.
 | 
			
		||||
# Instead, see the functional test `functional.test_cd_correction`
 | 
			
		||||
@@ -7,7 +7,8 @@ from thefuck.types import Command
 | 
			
		||||
    Command('cd foo', 'cd: foo: No such file or directory'),
 | 
			
		||||
    Command('cd foo/bar/baz',
 | 
			
		||||
            'cd: foo: No such file or directory'),
 | 
			
		||||
    Command('cd foo/bar/baz', 'cd: can\'t cd to foo/bar/baz')])
 | 
			
		||||
    Command('cd foo/bar/baz', 'cd: can\'t cd to foo/bar/baz'),
 | 
			
		||||
    Command('cd /foo/bar/', 'cd: The directory "/foo/bar/" does not exist')])
 | 
			
		||||
def test_match(command):
 | 
			
		||||
    assert match(command)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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'),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								tests/rules/test_git_commit_amend.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								tests/rules/test_git_commit_amend.py
									
									
									
									
									
										Normal 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'
 | 
			
		||||
							
								
								
									
										26
									
								
								tests/rules/test_git_merge.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								tests/rules/test_git_merge.py
									
									
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										25
									
								
								tests/rules/test_git_merge_unrelated.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								tests/rules/test_git_merge_unrelated.py
									
									
									
									
									
										Normal 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
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								tests/rules/test_unsudo.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								tests/rules/test_unsudo.py
									
									
									
									
									
										Normal 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
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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')
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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')
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -76,8 +76,8 @@ CONFIGURATION_TIMEOUT = 60
 | 
			
		||||
 | 
			
		||||
USER_COMMAND_MARK = u'\u200B' * 10
 | 
			
		||||
 | 
			
		||||
LOG_SIZE = 1000
 | 
			
		||||
 | 
			
		||||
LOG_SIZE_IN_BYTES = 1024 * 1024
 | 
			
		||||
 | 
			
		||||
LOG_SIZE_TO_CLEAN = 10 * 1024
 | 
			
		||||
 | 
			
		||||
DIFF_WITH_ALIAS = 0.5
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ from ..system import init_output
 | 
			
		||||
 | 
			
		||||
init_output()
 | 
			
		||||
 | 
			
		||||
import getpass  # noqa: E402
 | 
			
		||||
import os  # noqa: E402
 | 
			
		||||
import json  # noqa: E402
 | 
			
		||||
from tempfile import gettempdir  # noqa: E402
 | 
			
		||||
@@ -27,7 +28,9 @@ def _get_shell_pid():
 | 
			
		||||
 | 
			
		||||
def _get_not_configured_usage_tracker_path():
 | 
			
		||||
    """Returns path of special file where we store latest shell pid."""
 | 
			
		||||
    return Path(gettempdir()).joinpath('thefuck.last_not_configured_run')
 | 
			
		||||
    return Path(gettempdir()).joinpath(u'thefuck.last_not_configured_run_{}'.format(
 | 
			
		||||
        getpass.getuser(),
 | 
			
		||||
    ))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _record_first_run():
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,26 @@
 | 
			
		||||
import array
 | 
			
		||||
import fcntl
 | 
			
		||||
from functools import partial
 | 
			
		||||
import mmap
 | 
			
		||||
import os
 | 
			
		||||
import pty
 | 
			
		||||
import signal
 | 
			
		||||
import sys
 | 
			
		||||
import termios
 | 
			
		||||
import tty
 | 
			
		||||
from ..logs import warn
 | 
			
		||||
from .. import logs, const
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _read(f, fd):
 | 
			
		||||
    data = os.read(fd, 1024)
 | 
			
		||||
    f.write(data)
 | 
			
		||||
    f.flush()
 | 
			
		||||
    try:
 | 
			
		||||
        f.write(data)
 | 
			
		||||
    except ValueError:
 | 
			
		||||
        position = const.LOG_SIZE_IN_BYTES - const.LOG_SIZE_TO_CLEAN
 | 
			
		||||
        f.move(0, const.LOG_SIZE_TO_CLEAN, position)
 | 
			
		||||
        f.seek(position)
 | 
			
		||||
        f.write(b'\x00' * const.LOG_SIZE_TO_CLEAN)
 | 
			
		||||
        f.seek(position)
 | 
			
		||||
    return data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -61,10 +68,12 @@ def shell_logger(output):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    if not os.environ.get('SHELL'):
 | 
			
		||||
        warn("Shell logger doesn't support your platform.")
 | 
			
		||||
        logs.warn("Shell logger doesn't support your platform.")
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    with open(output, 'wb') as f:
 | 
			
		||||
        return_code = _spawn(os.environ['SHELL'], partial(_read, f))
 | 
			
		||||
    fd = os.open(output, os.O_CREAT | os.O_TRUNC | os.O_RDWR)
 | 
			
		||||
    os.write(fd, b'\x00' * const.LOG_SIZE_IN_BYTES)
 | 
			
		||||
    buffer = mmap.mmap(fd, const.LOG_SIZE_IN_BYTES, mmap.MAP_SHARED, mmap.PROT_WRITE)
 | 
			
		||||
    return_code = _spawn(os.environ['SHELL'], partial(_read, buffer))
 | 
			
		||||
 | 
			
		||||
    sys.exit(return_code)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
import os
 | 
			
		||||
import shlex
 | 
			
		||||
import mmap
 | 
			
		||||
import re
 | 
			
		||||
try:
 | 
			
		||||
    from shutil import get_terminal_size
 | 
			
		||||
except ImportError:
 | 
			
		||||
@@ -18,11 +20,6 @@ def _group_by_calls(log):
 | 
			
		||||
    script_line = None
 | 
			
		||||
    lines = []
 | 
			
		||||
    for line in log:
 | 
			
		||||
        try:
 | 
			
		||||
            line = line.decode()
 | 
			
		||||
        except UnicodeDecodeError:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        if const.USER_COMMAND_MARK in line or ps1_counter > 0:
 | 
			
		||||
            if script_line and ps1_counter == 0:
 | 
			
		||||
                yield script_line, lines
 | 
			
		||||
@@ -53,13 +50,14 @@ def _get_script_group_lines(grouped, script):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _get_output_lines(script, log_file):
 | 
			
		||||
    lines = log_file.readlines()[-const.LOG_SIZE:]
 | 
			
		||||
    data = log_file.read().decode()
 | 
			
		||||
    data = re.sub(r'\x00+$', '', data)
 | 
			
		||||
    lines = data.split('\n')
 | 
			
		||||
    grouped = list(_group_by_calls(lines))
 | 
			
		||||
    script_lines = _get_script_group_lines(grouped, script)
 | 
			
		||||
 | 
			
		||||
    screen = pyte.Screen(get_terminal_size().columns, len(script_lines))
 | 
			
		||||
    stream = pyte.Stream(screen)
 | 
			
		||||
    stream.feed(''.join(script_lines))
 | 
			
		||||
    stream.feed('\n'.join(script_lines))
 | 
			
		||||
    return screen.display
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -91,10 +89,11 @@ def get_output(script):
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        with logs.debug_time(u'Read output from log'), \
 | 
			
		||||
                open(os.environ['THEFUCK_OUTPUT_LOG'], 'rb') as log_file:
 | 
			
		||||
            _skip_old_lines(log_file)
 | 
			
		||||
            lines = _get_output_lines(script, log_file)
 | 
			
		||||
        with logs.debug_time(u'Read output from log'):
 | 
			
		||||
            fd = os.open(os.environ['THEFUCK_OUTPUT_LOG'], os.O_RDONLY)
 | 
			
		||||
            buffer = mmap.mmap(fd, const.LOG_SIZE_IN_BYTES, mmap.MAP_SHARED, mmap.PROT_READ)
 | 
			
		||||
            _skip_old_lines(buffer)
 | 
			
		||||
            lines = _get_output_lines(script, buffer)
 | 
			
		||||
            output = '\n'.join(lines).strip()
 | 
			
		||||
            logs.debug(u'Received output: {}'.format(output))
 | 
			
		||||
            return output
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										54
									
								
								thefuck/rules/adb_unknown_command.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								thefuck/rules/adb_unknown_command.py
									
									
									
									
									
										Normal 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)
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
from types import ModuleType
 | 
			
		||||
from thefuck.specific.apt import apt_available
 | 
			
		||||
from thefuck.utils import memoize, which
 | 
			
		||||
from thefuck.shells import shell
 | 
			
		||||
@@ -5,8 +6,14 @@ from thefuck.shells import shell
 | 
			
		||||
try:
 | 
			
		||||
    from CommandNotFound import CommandNotFound
 | 
			
		||||
 | 
			
		||||
    command_not_found = CommandNotFound()
 | 
			
		||||
    enabled_by_default = apt_available
 | 
			
		||||
 | 
			
		||||
    if isinstance(CommandNotFound, ModuleType):
 | 
			
		||||
        # For ubuntu 18.04+
 | 
			
		||||
        _get_packages = CommandNotFound.CommandNotFound().get_packages
 | 
			
		||||
    else:
 | 
			
		||||
        # For older versions
 | 
			
		||||
        _get_packages = CommandNotFound().getPackages
 | 
			
		||||
except ImportError:
 | 
			
		||||
    enabled_by_default = False
 | 
			
		||||
 | 
			
		||||
@@ -21,7 +28,7 @@ def _get_executable(command):
 | 
			
		||||
@memoize
 | 
			
		||||
def get_package(executable):
 | 
			
		||||
    try:
 | 
			
		||||
        packages = command_not_found.getPackages(executable)
 | 
			
		||||
        packages = _get_packages(executable)
 | 
			
		||||
        return packages[0][0]
 | 
			
		||||
    except IndexError:
 | 
			
		||||
        # IndexError is thrown when no matching package is found
 | 
			
		||||
 
 | 
			
		||||
@@ -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'
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								thefuck/rules/apt_upgrade.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								thefuck/rules/apt_upgrade.py
									
									
									
									
									
										Normal 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'
 | 
			
		||||
@@ -21,9 +21,12 @@ def _get_sub_dirs(parent):
 | 
			
		||||
@for_app('cd')
 | 
			
		||||
def match(command):
 | 
			
		||||
    """Match function copied from cd_mkdir.py"""
 | 
			
		||||
    return (command.script.startswith('cd ')
 | 
			
		||||
            and ('no such file or directory' in command.output.lower()
 | 
			
		||||
                 or 'cd: can\'t cd to' in command.output.lower()))
 | 
			
		||||
    return (
 | 
			
		||||
        command.script.startswith('cd ') and any((
 | 
			
		||||
            'no such file or directory' in command.output.lower(),
 | 
			
		||||
            'cd: can\'t cd to' in command.output.lower(),
 | 
			
		||||
            'does not exist' in command.output.lower()
 | 
			
		||||
        )))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@sudo_support
 | 
			
		||||
@@ -37,7 +40,11 @@ def get_new_command(command):
 | 
			
		||||
    dest = command.script_parts[1].split(os.sep)
 | 
			
		||||
    if dest[-1] == '':
 | 
			
		||||
        dest = dest[:-1]
 | 
			
		||||
    if six.PY2:
 | 
			
		||||
 | 
			
		||||
    if dest[0] == '':
 | 
			
		||||
        cwd = os.sep
 | 
			
		||||
        dest = dest[1:]
 | 
			
		||||
    elif six.PY2:
 | 
			
		||||
        cwd = os.getcwdu()
 | 
			
		||||
    else:
 | 
			
		||||
        cwd = os.getcwd()
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,11 @@ from thefuck.shells import shell
 | 
			
		||||
@for_app('cd')
 | 
			
		||||
def match(command):
 | 
			
		||||
    return (
 | 
			
		||||
        'no such file or directory' in command.output.lower()
 | 
			
		||||
        or 'cd: can\'t cd to' in command.output.lower()
 | 
			
		||||
        or 'the system cannot find the path specified.' in command.output.lower()
 | 
			
		||||
    )
 | 
			
		||||
        command.script.startswith('cd ') and any((
 | 
			
		||||
            'no such file or directory' in command.output.lower(),
 | 
			
		||||
            'cd: can\'t cd to' in command.output.lower(),
 | 
			
		||||
            'does not exist' in command.output.lower()
 | 
			
		||||
        )))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@sudo_support
 | 
			
		||||
 
 | 
			
		||||
@@ -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}'],
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								thefuck/rules/git_commit_amend.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								thefuck/rules/git_commit_amend.py
									
									
									
									
									
										Normal 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'
 | 
			
		||||
							
								
								
									
										18
									
								
								thefuck/rules/git_merge.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								thefuck/rules/git_merge.py
									
									
									
									
									
										Normal 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)
 | 
			
		||||
							
								
								
									
										12
									
								
								thefuck/rules/git_merge_unrelated.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								thefuck/rules/git_merge_unrelated.py
									
									
									
									
									
										Normal 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'
 | 
			
		||||
@@ -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))
 | 
			
		||||
 
 | 
			
		||||
@@ -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
									
								
							
							
						
						
									
										15
									
								
								thefuck/rules/unsudo.py
									
									
									
									
									
										Normal 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:])
 | 
			
		||||
@@ -20,9 +20,7 @@ shells = {'bash': Bash,
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _get_shell_from_env():
 | 
			
		||||
    path = os.environ.get('SHELL', '')
 | 
			
		||||
    base_name = os.path.basename(path)
 | 
			
		||||
    name = os.path.splitext(base_name)[0]
 | 
			
		||||
    name = os.environ.get('TF_SHELL')
 | 
			
		||||
 | 
			
		||||
    if name in shells:
 | 
			
		||||
        return shells[name]()
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ class Bash(Generic):
 | 
			
		||||
        return '''
 | 
			
		||||
            function {name} () {{
 | 
			
		||||
                TF_PYTHONIOENCODING=$PYTHONIOENCODING;
 | 
			
		||||
                export TF_SHELL=bash;
 | 
			
		||||
                export TF_ALIAS={name};
 | 
			
		||||
                export TF_SHELL_ALIASES=$(alias);
 | 
			
		||||
                export TF_HISTORY=$(fc -ln -10);
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ class Zsh(Generic):
 | 
			
		||||
        return '''
 | 
			
		||||
            {name} () {{
 | 
			
		||||
                TF_PYTHONIOENCODING=$PYTHONIOENCODING;
 | 
			
		||||
                export TF_SHELL=zsh;
 | 
			
		||||
                export TF_ALIAS={name};
 | 
			
		||||
                export TF_SHELL_ALIASES=$(alias);
 | 
			
		||||
                export TF_HISTORY="$(fc -ln -10)";
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user