Repository: nvbn/thefuck (Python)

Issue:
Python exception when executing rule git_hook_bypass: NoneType is not iterable
<!-- 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` :

The Fuck 3.31 using Python 3.9.6 and ZSH 5.8

Your system (Debian 7, ArchLinux, Windows, etc.):

MacOS Big Sur 11.5.1 (20G80)

How to reproduce the bug:
```
❯ git push
To ssh://prefix.url.com:1234/REPO/repo.git
 ! [rejected]                  AB-123-task -> AB-123-task (non-fast-forward)
error: failed to push some refs to 'ssh://prefix.url.com:1234/REPO/repo.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

❯ fuck
[WARN] Rule git_hook_bypass:
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/thefuck/types.py", line 181, in is_match
    if self.match(command):
  File "/usr/local/lib/python3.9/site-packages/decorator.py", line 232, in fun
    return caller(func, *(extras + args), **kw)
  File "/usr/local/lib/python3.9/site-packages/thefuck/specific/git.py", line 17, in git_support
    if 'trace: alias expansion:' in command.output:
TypeError: argument of type 'NoneType' is not iterable
----------------------------

No fucks given
```

The output of The Fuck with `THEFUCK_DEBUG=true` exported (typically execute `export THEFUCK_DEBUG=true` in your shell before The Fuck):
```
❯ git push
To ssh://prefix.url.com:1234/REPO/repo.git
 ! [rejected]                  AB-123-task -> AB-123-task (non-fast-forward)
error: failed to push some refs to 'ssh://prefix.url.com:1234/REPO/repo.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

❯ fuck
DEBUG: Run with settings: {'alter_history': True,
 'debug': True,
 'env': {'GIT_TRACE': '1', 'LANG': 'C', 'LC_ALL': 'C'},
 'exclude_rules': [],
 'excluded_search_path_prefixes': [],
 'history_limit': None,
 'instant_mode': False,
 'no_colors': False,
 'num_close_matches': 3,
 'priority': {'git_hook_bypass': 1001},
 'repeat': False,
 'require_confirmation': True,
 'rules': [<const: All rules enabled>],
 'slow_commands': ['lein', 'react-native', 'gradle', './gradlew', 'vagrant'],
 'user_dir': PosixPath('/Users/bbukaty/.config/thefuck'),
 'wait_command': 3,
 'wait_slow_command': 15}
DEBUG: Execution timed out!
DEBUG: Call: git push; with env: {redacted}; is slow: False took: 0:00:03.040546
DEBUG: Importing rule: adb_unknown_command; took: 0:00:00.000584
DEBUG: Importing rule: ag_literal; took: 0:00:00.000537
DEBUG: Importing rule: apt_get; took: 0:00:00.001473
DEBUG: Importing rule: apt_get_search; took: 0:00:00.000353
DEBUG: Importing rule: apt_invalid_operation; took: 0:00:00.000839
DEBUG: Importing rule: apt_list_upgradable; took: 0:00:00.000367
DEBUG: Importing rule: apt_upgrade; took: 0:00:00.001274
DEBUG: Importing rule: aws_cli; took: 0:00:00.000422
DEBUG: Importing rule: az_cli; took: 0:00:00.000383
DEBUG: Importing rule: brew_cask_dependency; took: 0:00:00.000850
DEBUG: Importing rule: brew_install; took: 0:00:00.000304
DEBUG: Importing rule: brew_link; took: 0:00:00.000350
DEBUG: Importing rule: brew_reinstall; took: 0:00:00.000704
DEBUG: Importing rule: brew_uninstall; took: 0:00:00.000377
DEBUG: Importing rule: brew_unknown_command; took: 0:00:00.000310
DEBUG: Importing rule: brew_update_formula; took: 0:00:00.000353
DEBUG: Importing rule: brew_upgrade; took: 0:00:00.000282
DEBUG: Importing rule: cargo; took: 0:00:00.000268
DEBUG: Importing rule: cargo_no_command; took: 0:00:00.000588
DEBUG: Importing rule: cat_dir; took: 0:00:00.000516
DEBUG: Importing rule: cd_correction; took: 0:00:00.001532
DEBUG: Importing rule: cd_cs; took: 0:00:00.000280
DEBUG: Importing rule: cd_mkdir; took: 0:00:00.000355
DEBUG: Importing rule: cd_parent; took: 0:00:00.000276
DEBUG: Importing rule: chmod_x; took: 0:00:00.000277
DEBUG: Importing rule: choco_install; took: 0:00:00.000538
DEBUG: Importing rule: composer_not_command; took: 0:00:00.000338
DEBUG: Importing rule: conda_mistype; took: 0:00:00.000333
DEBUG: Importing rule: cp_create_destination; took: 0:00:00.000369
DEBUG: Importing rule: cp_omitting_directory; took: 0:00:00.000375
DEBUG: Importing rule: cpp11; took: 0:00:00.000338
DEBUG: Importing rule: dirty_untar; took: 0:00:00.001920
DEBUG: Importing rule: dirty_unzip; took: 0:00:00.003105
DEBUG: Importing rule: django_south_ghost; took: 0:00:00.000336
DEBUG: Importing rule: django_south_merge; took: 0:00:00.000263
DEBUG: Importing rule: dnf_no_such_command; took: 0:00:00.000883
DEBUG: Importing rule: docker_image_being_used_by_container; took: 0:00:00.000342
DEBUG: Importing rule: docker_login; took: 0:00:00.000328
DEBUG: Importing rule: docker_not_command; took: 0:00:00.000496
DEBUG: Importing rule: dry; took: 0:00:00.000264
DEBUG: Importing rule: fab_command_not_found; took: 0:00:00.000403
DEBUG: Importing rule: fix_alt_space; took: 0:00:00.000324
DEBUG: Importing rule: fix_file; took: 0:00:00.002060
DEBUG: Importing rule: gem_unknown_command; took: 0:00:00.000445
DEBUG: Importing rule: git_add; took: 0:00:00.000714
DEBUG: Importing rule: git_add_force; took: 0:00:00.000309
DEBUG: Importing rule: git_bisect_usage; took: 0:00:00.000313
DEBUG: Importing rule: git_branch_delete; took: 0:00:00.000306
DEBUG: Importing rule: git_branch_delete_checked_out; took: 0:00:00.000313
DEBUG: Importing rule: git_branch_exists; took: 0:00:00.000323
DEBUG: Importing rule: git_branch_list; took: 0:00:00.000484
DEBUG: Importing rule: git_checkout; took: 0:00:00.000455
DEBUG: Importing rule: git_clone_git_clone; took: 0:00:00.000300
DEBUG: Importing rule: git_commit_amend; took: 0:00:00.000346
DEBUG: Importing rule: git_commit_reset; took: 0:00:00.000296
DEBUG: Importing rule: git_diff_no_index; took: 0:00:00.000306
DEBUG: Importing rule: git_diff_staged; took: 0:00:00.000299
DEBUG: Importing rule: git_fix_stash; took: 0:00:00.000366
DEBUG: Importing rule: git_flag_after_filename; took: 0:00:00.000305
DEBUG: Importing rule: git_help_aliased; took: 0:00:00.000310
DEBUG: Importing rule: git_hook_bypass; took: 0:00:00.000301
DEBUG: Importing rule: git_lfs_mistype; took: 0:00:00.000298
DEBUG: Importing rule: git_merge; took: 0:00:00.000370
DEBUG: Importing rule: git_merge_unrelated; took: 0:00:00.000302
DEBUG: Importing rule: git_not_command; took: 0:00:00.000319
DEBUG: Importing rule: git_pull; took: 0:00:00.000293
DEBUG: Importing rule: git_pull_clone; took: 0:00:00.000296
DEBUG: Importing rule: git_pull_uncommitted_changes; took: 0:00:00.000293
DEBUG: Importing rule: git_push; took: 0:00:00.000361
DEBUG: Importing rule: git_push_different_branch_names; took: 0:00:00.000308
DEBUG: Importing rule: git_push_force; took: 0:00:00.000322
DEBUG: Importing rule: git_push_pull; took: 0:00:00.000319
DEBUG: Importing rule: git_push_without_commits; took: 0:00:00.000511
DEBUG: Importing rule: git_rebase_merge_dir; took: 0:00:00.000353
DEBUG: Importing rule: git_rebase_no_changes; took: 0:00:00.000318
DEBUG: Importing rule: git_remote_delete; took: 0:00:00.000315
DEBUG: Importing rule: git_remote_seturl_add; took: 0:00:00.000288
DEBUG: Importing rule: git_rm_local_modifications; took: 0:00:00.000311
DEBUG: Importing rule: git_rm_recursive; took: 0:00:00.000320
DEBUG: Importing rule: git_rm_staged; took: 0:00:00.000297
DEBUG: Importing rule: git_stash; took: 0:00:00.000307
DEBUG: Importing rule: git_stash_pop; took: 0:00:00.000304
DEBUG: Importing rule: git_tag_force; took: 0:00:00.000522
DEBUG: Importing rule: git_two_dashes; took: 0:00:00.000435
DEBUG: Importing rule: go_run; took: 0:00:00.000305
DEBUG: Importing rule: go_unknown_command; took: 0:00:00.000464
DEBUG: Importing rule: gradle_no_task; took: 0:00:00.000525
DEBUG: Importing rule: gradle_wrapper; took: 0:00:00.000402
DEBUG: Importing rule: grep_arguments_order; took: 0:00:00.000344
DEBUG: Importing rule: grep_recursive; took: 0:00:00.000313
DEBUG: Importing rule: grunt_task_not_found; took: 0:00:00.000466
DEBUG: Importing rule: gulp_not_task; took: 0:00:00.000333
DEBUG: Importing rule: has_exists_script; took: 0:00:00.000366
DEBUG: Importing rule: heroku_multiple_apps; took: 0:00:00.000347
DEBUG: Importing rule: heroku_not_command; took: 0:00:00.000329
DEBUG: Importing rule: history; took: 0:00:00.000272
DEBUG: Importing rule: hostscli; took: 0:00:00.000338
DEBUG: Importing rule: ifconfig_device_not_found; took: 0:00:00.000317
DEBUG: Importing rule: java; took: 0:00:00.000301
DEBUG: Importing rule: javac; took: 0:00:00.000283
DEBUG: Importing rule: lein_not_task; took: 0:00:00.000316
DEBUG: Importing rule: ln_no_hard_link; took: 0:00:00.000292
DEBUG: Importing rule: ln_s_order; took: 0:00:00.000270
DEBUG: Importing rule: long_form_help; took: 0:00:00.000236
DEBUG: Importing rule: ls_all; took: 0:00:00.000356
DEBUG: Importing rule: ls_lah; took: 0:00:00.000288
DEBUG: Importing rule: man; took: 0:00:00.000283
DEBUG: Importing rule: man_no_space; took: 0:00:00.000232
DEBUG: Importing rule: mercurial; took: 0:00:00.000291
DEBUG: Importing rule: missing_space_before_subcommand; took: 0:00:00.000338
DEBUG: Importing rule: mkdir_p; took: 0:00:00.000290
DEBUG: Importing rule: mvn_no_command; took: 0:00:00.000292
DEBUG: Importing rule: mvn_unknown_lifecycle_phase; took: 0:00:00.000284
DEBUG: Importing rule: nixos_cmd_not_found; took: 0:00:00.000728
DEBUG: Importing rule: no_command; took: 0:00:00.000467
DEBUG: Importing rule: no_such_file; took: 0:00:00.000372
DEBUG: Importing rule: npm_missing_script; took: 0:00:00.000632
DEBUG: Importing rule: npm_run_script; took: 0:00:00.000298
DEBUG: Importing rule: npm_wrong_command; took: 0:00:00.000348
DEBUG: Importing rule: omnienv_no_such_command; took: 0:00:00.000534
DEBUG: Importing rule: open; took: 0:00:00.000352
DEBUG: Importing rule: pacman; took: 0:00:00.000675
DEBUG: Importing rule: pacman_invalid_option; took: 0:00:00.000346
DEBUG: Importing rule: pacman_not_found; took: 0:00:00.000341
DEBUG: Importing rule: path_from_history; took: 0:00:00.000281
DEBUG: Importing rule: php_s; took: 0:00:00.000284
DEBUG: Importing rule: pip_install; took: 0:00:00.000297
DEBUG: Importing rule: pip_unknown_command; took: 0:00:00.000287
DEBUG: Importing rule: port_already_in_use; took: 0:00:00.000335
DEBUG: Importing rule: prove_recursively; took: 0:00:00.000287
DEBUG: Importing rule: pyenv_no_such_command; took: 0:00:00.000324
DEBUG: Importing rule: python_command; took: 0:00:00.000269
DEBUG: Importing rule: python_execute; took: 0:00:00.000272
DEBUG: Importing rule: python_module_error; took: 0:00:00.000231
DEBUG: Importing rule: quotation_marks; took: 0:00:00.000246
DEBUG: Importing rule: react_native_command_unrecognized; took: 0:00:00.000304
DEBUG: Importing rule: remove_shell_prompt_literal; took: 0:00:00.000221
DEBUG: Importing rule: remove_trailing_cedilla; took: 0:00:00.000220
DEBUG: Importing rule: rm_dir; took: 0:00:00.000317
DEBUG: Importing rule: rm_root; took: 0:00:00.000271
DEBUG: Importing rule: scm_correction; took: 0:00:00.000279
DEBUG: Importing rule: sed_unterminated_s; took: 0:00:00.000279
DEBUG: Importing rule: sl_ls; took: 0:00:00.000223
DEBUG: Importing rule: ssh_known_hosts; took: 0:00:00.000284
DEBUG: Importing rule: sudo; took: 0:00:00.000225
DEBUG: Importing rule: sudo_command_from_user_path; took: 0:00:00.000275
DEBUG: Importing rule: switch_lang; took: 0:00:00.000299
DEBUG: Importing rule: systemctl; took: 0:00:00.000322
DEBUG: Importing rule: terraform_init; took: 0:00:00.000298
DEBUG: Importing rule: test.py; took: 0:00:00.000226
DEBUG: Importing rule: tmux; took: 0:00:00.000276
DEBUG: Importing rule: touch; took: 0:00:00.000281
DEBUG: Importing rule: tsuru_login; took: 0:00:00.000282
DEBUG: Importing rule: tsuru_not_command; took: 0:00:00.000280
DEBUG: Importing rule: unknown_command; took: 0:00:00.000228
DEBUG: Importing rule: unsudo; took: 0:00:00.000238
DEBUG: Importing rule: vagrant_up; took: 0:00:00.000280
DEBUG: Importing rule: whois; took: 0:00:00.000467
DEBUG: Importing rule: workon_doesnt_exists; took: 0:00:00.000304
DEBUG: Importing rule: yarn_alias; took: 0:00:00.000278
DEBUG: Importing rule: yarn_command_not_found; took: 0:00:00.000495
DEBUG: Importing rule: yarn_command_replaced; took: 0:00:00.000370
DEBUG: Importing rule: yarn_help; took: 0:00:00.000293
DEBUG: Importing rule: yum_invalid_operation; took: 0:00:00.000663
DEBUG: Trying rule: dirty_unzip; took: 0:00:00.000090
DEBUG: Trying rule: git_hook_bypass; took: 0:00:00.000029
[WARN] Rule git_hook_bypass:
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/thefuck/types.py", line 181, in is_match
    if self.match(command):
  File "/usr/local/lib/python3.9/site-packages/decorator.py", line 232, in fun
    return caller(func, *(extras + args), **kw)
  File "/usr/local/lib/python3.9/site-packages/thefuck/specific/git.py", line 17, in git_support
    if 'trace: alias expansion:' in command.output:
TypeError: argument of type 'NoneType' is not iterable
----------------------------

No fucks given
DEBUG: Total took: 0:00:03.145064
```


<!-- It's only with enough information that we can do something to fix the problem. -->



Discussion:
Hey! 👋 Thanks a lot for reporting.

So, what happens is that for some reason [the execution times out and therefore no output gets collected](https://github.com/nvbn/thefuck/blob/master/thefuck/output_readers/rerun.py#L67-L68). As the `git_hook_bypass` rule [does not require output](https://github.com/nvbn/thefuck/blob/2a166a7dec3c872ef6be3870b50889500ca6a212/thefuck/rules/git_hook_bypass.py#L27), it gets triggered. But then the `git_support` decorator — used not only [by `git_hook_bypass`](https://github.com/nvbn/thefuck/blob/2a166a7dec3c872ef6be3870b50889500ca6a212/thefuck/rules/git_hook_bypass.py#L7) but by all Git rules — [does depend on output](https://github.com/nvbn/thefuck/blob/2a166a7dec3c872ef6be3870b50889500ca6a212/thefuck/specific/git.py#L17), which is `None` in this case. Hence the error.

The fix is rather simple. Would you be willing to contribute? 🙂 
sure.

I roughly understand what you've described so far, but not enough to guess the solution you have in mind: what is it?
Awesome! 🙌💥

TL;DR: please make `git_support` work for cases where there's no output 🙂 

It's a good idea to support Git rules that do not depend on output — as is the case of the `git_hook_bypass`. That in turn rules out one possible solution which would be making `git_hook_bypass` output-dependable.

So, that means we need to make `git_support` work for cases where there's no output — such as a case where a git hook that runs tests takes too long to run and you just want to have a quick dirty commit or simply push code to a temporary branch on a remote.

Thing is that the `git_support` decorator not only validates the name of the command (either `git` or `hub`) but also converts an alias to its respective expansion — e.g. it can convert `git l` into `git log`. And that is dependent on Git's output — more specifically on Git's trace output, something you can enable by setting a variable before running Git — `export GIT_TRACE=1`.

It's nice to have the alias converted so TheFuck can be more assertive when matching rules. But if no output is available to `git_support` and the command does not involve an alias (think `git push`) and the rule does not depend on an output to match or generate a new command, the alias expansion can be skipped.

I hope that helps. I can be more specific if you wish 😊



Patch:
diff --git a/thefuck/specific/git.py b/thefuck/specific/git.py
index c8f11bd64..979314651 100644
--- a/thefuck/specific/git.py
+++ b/thefuck/specific/git.py
@@ -14,7 +14,7 @@ def git_support(fn, command):
         return False
 
     # perform git aliases expansion
-    if 'trace: alias expansion:' in command.output:
+    if command.output and 'trace: alias expansion:' in command.output:
         search = re.search("trace: alias expansion: ([^ ]*) => ([^\n]*)",
                            command.output)
         alias = search.group(1)


Test patch:
diff --git a/tests/specific/test_git.py b/tests/specific/test_git.py
index a17121082..10acb6cde 100644
--- a/tests/specific/test_git.py
+++ b/tests/specific/test_git.py
@@ -27,9 +27,10 @@ def fn(command):
     ('ls', False),
     ('cat git', False),
     ('cat hub', False)])
-def test_git_support_match(command, is_git):
+@pytest.mark.parametrize('output', ['', None])
+def test_git_support_match(command, is_git, output):
     @git_support
     def fn(command):
         return True
 
-    assert fn(Command(command, '')) == is_git
+    assert fn(Command(command, output)) == is_git