Merge pull request #180 from pre-commit/require_new_autopep8

Require a newer version of autopep8
diff --git a/.gitignore b/.gitignore
index 2626934..6fdf044 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
 *.iml
 *.py[co]
 .*.sw[a-z]
+.cache
 .coverage
 .idea
 .project
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ccfdc4b..f39150b 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,5 +1,5 @@
 -   repo: https://github.com/pre-commit/pre-commit-hooks
-    sha: v0.7.0
+    sha: v0.9.1
     hooks:
     -   id: trailing-whitespace
     -   id: end-of-file-fixer
@@ -13,7 +13,7 @@
     -   id: requirements-txt-fixer
     -   id: flake8
 -   repo: https://github.com/pre-commit/pre-commit
-    sha: v0.12.2
+    sha: v0.15.0
     hooks:
     -   id: validate_config
     -   id: validate_manifest
diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml
index 0604585..1aba741 100644
--- a/.pre-commit-hooks.yaml
+++ b/.pre-commit-hooks.yaml
@@ -3,147 +3,235 @@
     description: "Runs autopep8 over python source.  If you configure additional arguments you'll want to at least include -i."
     entry: autopep8-wrapper
     language: python
-    files: \.py$
+    types: [python]
     args: [-i]
+    # for backward compatibility
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-added-large-files
     name: Check for added large files
     description: Prevent giant files from being committed
     entry: check-added-large-files
     language: python
-    # Match all files
+    # for backward compatibility
     files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-ast
     name: Check python ast
     description: Simply check whether the files parse as valid python.
     entry: check-ast
     language: python
-    files: '\.py$'
+    types: [python]
+    # for backward compatibility
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-byte-order-marker
     name: Check for byte-order marker
     description: Forbid files which have a UTF-8 byte-order marker
     entry: check-byte-order-marker
     language: python
-    files: '\.py$'
+    types: [python]
+    # for backward compatibility
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-case-conflict
     name: Check for case conflicts
     description: Check for files that would conflict in case-insensitive filesystems
     entry: check-case-conflict
     language: python
-    # Match all files
+    # for backward compatibility
     files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-docstring-first
     name: Check docstring is first
     description: Checks a common error of defining a docstring after code.
     entry: check-docstring-first
     language: python
-    files: \.py$
+    types: [python]
+    # for backward compatibility
+    files: ''
+    minimum_pre_commit_version: 0.15.0
+-   id: check-executables-have-shebangs
+    name: Check that executables have shebangs
+    description: Ensures that (non-binary) executables have a shebang.
+    entry: check-executables-have-shebangs
+    language: python
+    types: [text, executable]
+    # for backward compatibility
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-json
     name: Check JSON
     description: This hook checks json files for parseable syntax.
     entry: check-json
     language: python
-    files: \.json$
+    types: [json]
+    # for backward compatibility
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: pretty-format-json
     name: Pretty format JSON
     description: This hook sets a standard for formatting JSON files.
     entry: pretty-format-json
     language: python
-    files: \.json$
+    types: [json]
+    # for backward compatibility
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-merge-conflict
     name: Check for merge conflicts
     description: Check for files that contain merge conflict strings.
     entry: check-merge-conflict
     language: python
-    # Match all files
+    types: [text]
+    # for backward compatibility
     files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-symlinks
     name: Check for broken symlinks
     description: Checks for symlinks which do not point to anything.
     entry: check-symlinks
     language: python
-    # Match all files
+    types: [symlink]
+    # for backward compatibility
     files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-xml
     name: Check Xml
     description: This hook checks xml files for parseable syntax.
     entry: check-xml
     language: python
-    files: \.xml$
+    types: [xml]
+    # for backward compatibility
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-yaml
     name: Check Yaml
     description: This hook checks yaml files for parseable syntax.
     entry: check-yaml
     language: python
-    files: \.(yaml|yml|eyaml)$
+    types: [yaml]
+    # for backward compatibility
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: debug-statements
     name: Debug Statements (Python)
     description: This hook checks that debug statements (pdb, ipdb, pudb) are not imported on commit.
     entry: debug-statement-hook
     language: python
-    files: \.py$
+    types: [python]
+    # for backward compatibility
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: detect-aws-credentials
     name: Detect AWS Credentials
     description: Detects *your* aws credentials from the aws cli credentials file
     entry: detect-aws-credentials
     language: python
+    types: [text]
+    # for backward compatibility
     files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: detect-private-key
     name: Detect Private Key
     description: Detects the presence of private keys
     entry: detect-private-key
     language: python
+    types: [text]
+    # for backward compatibility
     files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: double-quote-string-fixer
     name: Fix double quoted strings
     description: This hook replaces double quoted strings with single quoted strings
     entry: double-quote-string-fixer
     language: python
-    files: \.py$
+    types: [python]
+    # for backward compatibility
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: end-of-file-fixer
     name: Fix End of Files
     description: Ensures that a file is either empty, or ends with one newline.
     entry: end-of-file-fixer
     language: python
-    files: \.(asciidoc|adoc|coffee|cpp|css|c|ejs|erb|groovy|h|haml|hh|hpp|hxx|html|in|j2|jade|json|js|less|markdown|md|ml|mli|pp|py|rb|rs|R|scala|scss|sh|slim|tex|tmpl|ts|txt|yaml|yml)$
+    types: [text]
+    # for backward compatibility
+    files: ''
+    minimum_pre_commit_version: 0.15.0
+-   id: file-contents-sorter
+    name: File Contents Sorter
+    description: Sort the lines in specified files (defaults to alphabetical). You must provide list of target files as input in your .pre-commit-config.yaml file.
+    entry: file-contents-sorter
+    language: python
+    files: '^$'
 -   id: fix-encoding-pragma
     name: Fix python encoding pragma
     language: python
     entry: fix-encoding-pragma
     description: 'Add # -*- coding: utf-8 -*- to the top of python files'
-    files: \.py$
+    types: [python]
+    # for backward compatibility
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: flake8
     name: Flake8
     description: This hook runs flake8.
     entry: flake8
     language: python
-    files: \.py$
+    types: [python]
+    # for backward compatibility
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: forbid-new-submodules
     name: Forbid new submodules
     language: python
     entry: forbid-new-submodules
     description: Prevent addition of new git submodules
+    # for backward compatibility
     files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: name-tests-test
     name: Tests should end in _test.py
     description: This verifies that test files are named correctly
     entry: name-tests-test
     language: python
     files: tests/.+\.py$
+-   id: no-commit-to-branch
+    name: "Don't commit to branch"
+    entry: no-commit-to-branch
+    language: python
+    always_run: true
+    # for backward compatibility
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: pyflakes
     name: Pyflakes (DEPRECATED, use flake8)
     description: This hook runs pyflakes. (This is deprecated, use flake8).
     entry: pyflakes
     language: python
-    files: \.py$
+    types: [python]
+    # for backward compatibility
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: requirements-txt-fixer
     name: Fix requirements.txt
     description: Sorts entries in requirements.txt
     entry: requirements-txt-fixer
     language: python
     files: requirements.*\.txt$
+-   id: sort-simple-yaml
+    name: Sort simple YAML files
+    language: python
+    entry: sort-simple-yaml
+    description: Sorts simple YAML files which consist only of top-level keys, preserving comments and blocks.
+    files: '^$'
 -   id: trailing-whitespace
     name: Trim Trailing Whitespace
     description: This hook trims trailing whitespace.
     entry: trailing-whitespace-fixer
     language: python
-    files: \.(asciidoc|adoc|coffee|cpp|css|c|ejs|erb|groovy|h|haml|hh|hpp|hxx|html|in|j2|jade|json|js|less|markdown|md|ml|mli|pp|py|rb|rs|R|scala|scss|sh|slim|tex|tmpl|ts|txt|yaml|yml)$
+    types: [text]
+    # for backward compatibility
+    files: ''
+    minimum_pre_commit_version: 0.15.0
diff --git a/CHANGELOG b/CHANGELOG
index c489206..688ef2b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,24 @@
+0.9.1
+=====
+- Add `check-executables-have-shebangs` hook.
+
+0.9.0
+=====
+- Add `sort-simple-yaml` hook
+- Fix `requirements-txt-fixer` for empty files
+- Add `file-contents-sorter` hook for sorting flat files
+- `check-merge-conflict` now recognizes rebase conflicts
+- Metadata now uses `types` (and therefore requires pre-commit 0.15.0).  This
+  allows the text processing hooks to match *all* text files (and to match
+  files which would only be classifiable by their shebangs).
+
+0.8.0
+=====
+- Add flag allowing missing keys to `detect-aws-credentials`
+- Handle django default `tests.py` in `name-tests-test`
+- Add `--no-ensure-ascii` option to `pretty-format-json`
+- Add `no-commit-to-branch` hook
+
 0.7.1
 =====
 - Don't false positive on files where trailing whitespace isn't changed.
diff --git a/README.md b/README.md
index 0650ca2..55b49ff 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
 Add this to your `.pre-commit-config.yaml`
 
     -   repo: git://github.com/pre-commit/pre-commit-hooks
-        sha: v0.7.1  # Use the ref you want to point at
+        sha: v0.9.1  # Use the ref you want to point at
         hooks:
         -   id: trailing-whitespace
         # -   id: ...
@@ -35,6 +35,8 @@
   case-insensitive filesystem like MacOS HFS+ or Windows FAT.
 - `check-docstring-first` - Checks for a common error of placing code before
   the docstring.
+- `check-executables-have-shebangs` - Checks that non-binary executables have a
+  proper shebang.
 - `check-json` - Attempts to load all json files to verify syntax.
 - `check-merge-conflict` - Check for files that contain merge conflict strings.
 - `check-symlinks` - Checks for symlinks which do not point to anything.
@@ -52,11 +54,14 @@
   with single quoted strings.
 - `end-of-file-fixer` - Makes sure files end in a newline and only a newline.
 - `fix-encoding-pragma` - Add `# -*- coding: utf-8 -*-` to the top of python files.
+- `file-contents-sorter` - Sort the lines in specified files (defaults to alphabetical). You must provide list of target files as input to it. Note that this hook WILL remove blank lines and does NOT respect any comments.
     - To remove the coding pragma pass `--remove` (useful in a python3-only codebase)
 - `flake8` - Run flake8 on your python files.
 - `forbid-new-submodules` - Prevent addition of new git submodules.
 - `name-tests-test` - Assert that files in tests/ end in `_test.py`.
     - Use `args: ['--django']` to match `test*.py` instead.
+- `no-commit-to-branch` - Protect specific branches from direct checkins.
+    - Use `args: -b <branch> ` to set the branch. `master` is the default if no argument is set.
 - `pyflakes` - Run pyflakes on your python files.
 - `pretty-format-json` - Checks that all your JSON files are pretty.  "Pretty"
   here means that keys are sorted and indented.  You can configure this with
@@ -66,6 +71,7 @@
     - `--no-sort-keys` - when autofixing, retain the original key ordering (instead of sorting the keys)
     - `--top-keys comma,separated,keys` - Keys to keep at the top of mappings.
 - `requirements-txt-fixer` - Sorts entries in requirements.txt
+- `sort-simple-yaml` - Sorts simple YAML files which consist only of top-level keys, preserving comments and blocks.
 - `trailing-whitespace` - Trims trailing whitespace.
     - Markdown linebreak trailing spaces preserved for `.md` and`.markdown`;
       use `args: ['--markdown-linebreak-ext=txt,text']` to add other extensions,
diff --git a/hooks.yaml b/hooks.yaml
index 0604585..5278bf5 100644
--- a/hooks.yaml
+++ b/hooks.yaml
@@ -1,149 +1,168 @@
 -   id: autopep8-wrapper
-    name: autopep8 wrapper
-    description: "Runs autopep8 over python source.  If you configure additional arguments you'll want to at least include -i."
-    entry: autopep8-wrapper
-    language: python
-    files: \.py$
-    args: [-i]
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-added-large-files
-    name: Check for added large files
-    description: Prevent giant files from being committed
-    entry: check-added-large-files
-    language: python
-    # Match all files
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
     files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-ast
-    name: Check python ast
-    description: Simply check whether the files parse as valid python.
-    entry: check-ast
-    language: python
-    files: '\.py$'
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-byte-order-marker
-    name: Check for byte-order marker
-    description: Forbid files which have a UTF-8 byte-order marker
-    entry: check-byte-order-marker
-    language: python
-    files: '\.py$'
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-case-conflict
-    name: Check for case conflicts
-    description: Check for files that would conflict in case-insensitive filesystems
-    entry: check-case-conflict
-    language: python
-    # Match all files
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
     files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-docstring-first
-    name: Check docstring is first
-    description: Checks a common error of defining a docstring after code.
-    entry: check-docstring-first
-    language: python
-    files: \.py$
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
+-   id: check-executables-have-shebangs
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-json
-    name: Check JSON
-    description: This hook checks json files for parseable syntax.
-    entry: check-json
-    language: python
-    files: \.json$
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: pretty-format-json
-    name: Pretty format JSON
-    description: This hook sets a standard for formatting JSON files.
-    entry: pretty-format-json
-    language: python
-    files: \.json$
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-merge-conflict
-    name: Check for merge conflicts
-    description: Check for files that contain merge conflict strings.
-    entry: check-merge-conflict
-    language: python
-    # Match all files
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
     files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-symlinks
-    name: Check for broken symlinks
-    description: Checks for symlinks which do not point to anything.
-    entry: check-symlinks
-    language: python
-    # Match all files
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
     files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-xml
-    name: Check Xml
-    description: This hook checks xml files for parseable syntax.
-    entry: check-xml
-    language: python
-    files: \.xml$
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: check-yaml
-    name: Check Yaml
-    description: This hook checks yaml files for parseable syntax.
-    entry: check-yaml
-    language: python
-    files: \.(yaml|yml|eyaml)$
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: debug-statements
-    name: Debug Statements (Python)
-    description: This hook checks that debug statements (pdb, ipdb, pudb) are not imported on commit.
-    entry: debug-statement-hook
-    language: python
-    files: \.py$
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: detect-aws-credentials
-    name: Detect AWS Credentials
-    description: Detects *your* aws credentials from the aws cli credentials file
-    entry: detect-aws-credentials
-    language: python
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
     files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: detect-private-key
-    name: Detect Private Key
-    description: Detects the presence of private keys
-    entry: detect-private-key
-    language: python
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
     files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: double-quote-string-fixer
-    name: Fix double quoted strings
-    description: This hook replaces double quoted strings with single quoted strings
-    entry: double-quote-string-fixer
-    language: python
-    files: \.py$
--   id: end-of-file-fixer
-    name: Fix End of Files
-    description: Ensures that a file is either empty, or ends with one newline.
-    entry: end-of-file-fixer
-    language: python
-    files: \.(asciidoc|adoc|coffee|cpp|css|c|ejs|erb|groovy|h|haml|hh|hpp|hxx|html|in|j2|jade|json|js|less|markdown|md|ml|mli|pp|py|rb|rs|R|scala|scss|sh|slim|tex|tmpl|ts|txt|yaml|yml)$
--   id: fix-encoding-pragma
-    name: Fix python encoding pragma
-    language: python
-    entry: fix-encoding-pragma
-    description: 'Add # -*- coding: utf-8 -*- to the top of python files'
-    files: \.py$
--   id: flake8
-    name: Flake8
-    description: This hook runs flake8.
-    entry: flake8
-    language: python
-    files: \.py$
--   id: forbid-new-submodules
-    name: Forbid new submodules
-    language: python
-    entry: forbid-new-submodules
-    description: Prevent addition of new git submodules
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
     files: ''
+    minimum_pre_commit_version: 0.15.0
+-   id: end-of-file-fixer
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
+-   id: file-contents-sorter
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
+-   id: fix-encoding-pragma
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
+-   id: flake8
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
+-   id: forbid-new-submodules
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: name-tests-test
-    name: Tests should end in _test.py
-    description: This verifies that test files are named correctly
-    entry: name-tests-test
-    language: python
-    files: tests/.+\.py$
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
+-   id: no-commit-to-branch
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: pyflakes
-    name: Pyflakes (DEPRECATED, use flake8)
-    description: This hook runs pyflakes. (This is deprecated, use flake8).
-    entry: pyflakes
-    language: python
-    files: \.py$
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: requirements-txt-fixer
-    name: Fix requirements.txt
-    description: Sorts entries in requirements.txt
-    entry: requirements-txt-fixer
-    language: python
-    files: requirements.*\.txt$
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
+-   id: sort-simple-yaml
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
 -   id: trailing-whitespace
-    name: Trim Trailing Whitespace
-    description: This hook trims trailing whitespace.
-    entry: trailing-whitespace-fixer
-    language: python
-    files: \.(asciidoc|adoc|coffee|cpp|css|c|ejs|erb|groovy|h|haml|hh|hpp|hxx|html|in|j2|jade|json|js|less|markdown|md|ml|mli|pp|py|rb|rs|R|scala|scss|sh|slim|tex|tmpl|ts|txt|yaml|yml)$
+    language: system
+    name: upgrade-your-pre-commit-version
+    entry: upgrade-your-pre-commit-version
+    files: ''
+    minimum_pre_commit_version: 0.15.0
diff --git a/pre_commit_hooks/check_executables_have_shebangs.py b/pre_commit_hooks/check_executables_have_shebangs.py
new file mode 100644
index 0000000..89ac6e5
--- /dev/null
+++ b/pre_commit_hooks/check_executables_have_shebangs.py
@@ -0,0 +1,40 @@
+"""Check that executable text files have a shebang."""
+from __future__ import absolute_import
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import argparse
+import pipes
+import sys
+
+
+def check_has_shebang(path):
+    with open(path, 'rb') as f:
+        first_bytes = f.read(2)
+
+    if first_bytes != b'#!':
+        print(
+            '{path}: marked executable but has no (or invalid) shebang!\n'
+            "  If it isn't supposed to be executable, try: chmod -x {quoted}\n"
+            '  If it is supposed to be executable, double-check its shebang.'.format(
+                path=path,
+                quoted=pipes.quote(path),
+            ),
+            file=sys.stderr,
+        )
+        return 1
+    else:
+        return 0
+
+
+def main(argv=None):
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument('filenames', nargs='*')
+    args = parser.parse_args(argv)
+
+    retv = 0
+
+    for filename in args.filenames:
+        retv |= check_has_shebang(filename)
+
+    return retv
diff --git a/pre_commit_hooks/check_json.py b/pre_commit_hooks/check_json.py
index e1578ff..b403f4b 100644
--- a/pre_commit_hooks/check_json.py
+++ b/pre_commit_hooks/check_json.py
@@ -1,10 +1,10 @@
 from __future__ import print_function
 
 import argparse
+import io
+import json
 import sys
 
-import simplejson
-
 
 def check_json(argv=None):
     parser = argparse.ArgumentParser()
@@ -14,8 +14,8 @@
     retval = 0
     for filename in args.filenames:
         try:
-            simplejson.load(open(filename))
-        except (simplejson.JSONDecodeError, UnicodeDecodeError) as exc:
+            json.load(io.open(filename, encoding='UTF-8'))
+        except (ValueError, UnicodeDecodeError) as exc:
             print('{}: Failed to json decode ({})'.format(filename, exc))
             retval = 1
     return retval
diff --git a/pre_commit_hooks/check_merge_conflict.py b/pre_commit_hooks/check_merge_conflict.py
index d986998..7d87efc 100644
--- a/pre_commit_hooks/check_merge_conflict.py
+++ b/pre_commit_hooks/check_merge_conflict.py
@@ -15,7 +15,11 @@
 def is_in_merge():
     return (
         os.path.exists(os.path.join('.git', 'MERGE_MSG')) and
-        os.path.exists(os.path.join('.git', 'MERGE_HEAD'))
+        (
+            os.path.exists(os.path.join('.git', 'MERGE_HEAD')) or
+            os.path.exists(os.path.join('.git', 'rebase-apply')) or
+            os.path.exists(os.path.join('.git', 'rebase-merge'))
+        )
     )
 
 
diff --git a/pre_commit_hooks/file_contents_sorter.py b/pre_commit_hooks/file_contents_sorter.py
new file mode 100644
index 0000000..fe7f7ee
--- /dev/null
+++ b/pre_commit_hooks/file_contents_sorter.py
@@ -0,0 +1,52 @@
+"""
+A very simple pre-commit hook that, when passed one or more filenames
+as arguments, will sort the lines in those files.
+
+An example use case for this: you have a deploy-whitelist.txt file
+in a repo that contains a list of filenames that is used to specify
+files to be included in a docker container. This file has one filename
+per line. Various users are adding/removing lines from this file; using
+this hook on that file should reduce the instances of git merge
+conflicts and keep the file nicely ordered.
+"""
+from __future__ import print_function
+
+import argparse
+
+PASS = 0
+FAIL = 1
+
+
+def sort_file_contents(f):
+    before = list(f)
+    after = sorted([line.strip(b'\n\r') for line in before if line.strip()])
+
+    before_string = b''.join(before)
+    after_string = b'\n'.join(after) + b'\n'
+
+    if before_string == after_string:
+        return PASS
+    else:
+        f.seek(0)
+        f.write(after_string)
+        f.truncate()
+        return FAIL
+
+
+def main(argv=None):
+    parser = argparse.ArgumentParser()
+    parser.add_argument('filenames', nargs='+', help='Files to sort')
+    args = parser.parse_args(argv)
+
+    retv = PASS
+
+    for arg in args.filenames:
+        with open(arg, 'rb+') as file_obj:
+            ret_for_file = sort_file_contents(file_obj)
+
+            if ret_for_file:
+                print('Sorting {}'.format(arg))
+
+            retv |= ret_for_file
+
+    return retv
diff --git a/pre_commit_hooks/no_commit_to_branch.py b/pre_commit_hooks/no_commit_to_branch.py
new file mode 100644
index 0000000..4aa3535
--- /dev/null
+++ b/pre_commit_hooks/no_commit_to_branch.py
@@ -0,0 +1,25 @@
+from __future__ import print_function
+
+import argparse
+import sys
+
+from pre_commit_hooks.util import cmd_output
+
+
+def is_on_branch(protected):
+    branch = cmd_output('git', 'symbolic-ref', 'HEAD')
+    chunks = branch.strip().split('/')
+    return '/'.join(chunks[2:]) == protected
+
+
+def main(argv=[]):
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        '-b', '--branch', default='master', help='branch to disallow commits to')
+    args = parser.parse_args(argv)
+
+    return int(is_on_branch(args.branch))
+
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv))
diff --git a/pre_commit_hooks/pretty_format_json.py b/pre_commit_hooks/pretty_format_json.py
index bf1ccb1..5e04230 100644
--- a/pre_commit_hooks/pretty_format_json.py
+++ b/pre_commit_hooks/pretty_format_json.py
@@ -1,13 +1,15 @@
 from __future__ import print_function
 
 import argparse
+import io
 import sys
 from collections import OrderedDict
 
 import simplejson
+import six
 
 
-def _get_pretty_format(contents, indent, sort_keys=True, top_keys=[]):
+def _get_pretty_format(contents, indent, ensure_ascii=True, sort_keys=True, top_keys=[]):
     def pairs_first(pairs):
         before = [pair for pair in pairs if pair[0] in top_keys]
         before = sorted(before, key=lambda x: top_keys.index(x[0]))
@@ -15,18 +17,19 @@
         if sort_keys:
             after = sorted(after, key=lambda x: x[0])
         return OrderedDict(before + after)
-    return simplejson.dumps(
+    return six.text_type(simplejson.dumps(
         simplejson.loads(
             contents,
             object_pairs_hook=pairs_first,
         ),
-        indent=indent
-    ) + "\n"  # dumps don't end with a newline
+        indent=indent,
+        ensure_ascii=ensure_ascii,
+    )) + "\n"  # dumps does not end with a newline
 
 
 def _autofix(filename, new_contents):
     print("Fixing file {}".format(filename))
-    with open(filename, 'w') as f:
+    with io.open(filename, 'w', encoding='UTF-8') as f:
         f.write(new_contents)
 
 
@@ -70,6 +73,13 @@
         help='String used as delimiter for one indentation level',
     )
     parser.add_argument(
+        '--no-ensure-ascii',
+        action='store_true',
+        dest='no_ensure_ascii',
+        default=False,
+        help='Do NOT convert non-ASCII characters to Unicode escape sequences (\\uXXXX)',
+    )
+    parser.add_argument(
         '--no-sort-keys',
         action='store_true',
         dest='no_sort_keys',
@@ -90,13 +100,13 @@
     status = 0
 
     for json_file in args.filenames:
-        with open(json_file) as f:
+        with io.open(json_file, encoding='UTF-8') as f:
             contents = f.read()
 
         try:
             pretty_contents = _get_pretty_format(
-                contents, args.indent, sort_keys=not args.no_sort_keys,
-                top_keys=args.top_keys
+                contents, args.indent, ensure_ascii=not args.no_ensure_ascii,
+                sort_keys=not args.no_sort_keys, top_keys=args.top_keys,
             )
 
             if contents != pretty_contents:
diff --git a/pre_commit_hooks/requirements_txt_fixer.py b/pre_commit_hooks/requirements_txt_fixer.py
index efa1906..ffabf2a 100644
--- a/pre_commit_hooks/requirements_txt_fixer.py
+++ b/pre_commit_hooks/requirements_txt_fixer.py
@@ -3,6 +3,10 @@
 import argparse
 
 
+PASS = 0
+FAIL = 1
+
+
 class Requirement(object):
 
     def __init__(self):
@@ -30,21 +34,25 @@
 
 def fix_requirements(f):
     requirements = []
-    before = []
+    before = tuple(f)
     after = []
 
-    for line in f:
-        before.append(line)
+    before_string = b''.join(before)
 
-        # If the most recent requirement object has a value, then it's time to
-        # start building the next requirement object.
+    # If the file is empty (i.e. only whitespace/newlines) exit early
+    if before_string.strip() == b'':
+        return PASS
+
+    for line in before:
+        # If the most recent requirement object has a value, then it's
+        # time to start building the next requirement object.
         if not len(requirements) or requirements[-1].value is not None:
             requirements.append(Requirement())
 
         requirement = requirements[-1]
 
-        # If we see a newline before any requirements, then this is a top of
-        # file comment.
+        # If we see a newline before any requirements, then this is a
+        # top of file comment.
         if len(requirements) == 1 and line.strip() == b'':
             if len(requirement.comments) and requirement.comments[0].startswith(b'#'):
                 requirement.value = b'\n'
@@ -56,20 +64,18 @@
             requirement.value = line
 
     for requirement in sorted(requirements):
-        for comment in requirement.comments:
-            after.append(comment)
+        after.extend(requirement.comments)
         after.append(requirement.value)
 
-    before_string = b''.join(before)
     after_string = b''.join(after)
 
     if before_string == after_string:
-        return 0
+        return PASS
     else:
         f.seek(0)
         f.write(after_string)
         f.truncate()
-        return 1
+        return FAIL
 
 
 def fix_requirements_txt(argv=None):
@@ -77,7 +83,7 @@
     parser.add_argument('filenames', nargs='*', help='Filenames to fix')
     args = parser.parse_args(argv)
 
-    retv = 0
+    retv = PASS
 
     for arg in args.filenames:
         with open(arg, 'rb+') as file_obj:
diff --git a/pre_commit_hooks/sort_simple_yaml.py b/pre_commit_hooks/sort_simple_yaml.py
new file mode 100755
index 0000000..7afae91
--- /dev/null
+++ b/pre_commit_hooks/sort_simple_yaml.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python
+"""Sort a simple YAML file, keeping blocks of comments and definitions
+together.
+
+We assume a strict subset of YAML that looks like:
+
+    # block of header comments
+    # here that should always
+    # be at the top of the file
+
+    # optional comments
+    # can go here
+    key: value
+    key: value
+
+    key: value
+
+In other words, we don't sort deeper than the top layer, and might corrupt
+complicated YAML files.
+"""
+from __future__ import print_function
+
+import argparse
+
+
+QUOTES = ["'", '"']
+
+
+def sort(lines):
+    """Sort a YAML file in alphabetical order, keeping blocks together.
+
+    :param lines: array of strings (without newlines)
+    :return: sorted array of strings
+    """
+    # make a copy of lines since we will clobber it
+    lines = list(lines)
+    new_lines = parse_block(lines, header=True)
+
+    for block in sorted(parse_blocks(lines), key=first_key):
+        if new_lines:
+            new_lines.append('')
+        new_lines.extend(block)
+
+    return new_lines
+
+
+def parse_block(lines, header=False):
+    """Parse and return a single block, popping off the start of `lines`.
+
+    If parsing a header block, we stop after we reach a line that is not a
+    comment. Otherwise, we stop after reaching an empty line.
+
+    :param lines: list of lines
+    :param header: whether we are parsing a header block
+    :return: list of lines that form the single block
+    """
+    block_lines = []
+    while lines and lines[0] and (not header or lines[0].startswith('#')):
+        block_lines.append(lines.pop(0))
+    return block_lines
+
+
+def parse_blocks(lines):
+    """Parse and return all possible blocks, popping off the start of `lines`.
+
+    :param lines: list of lines
+    :return: list of blocks, where each block is a list of lines
+    """
+    blocks = []
+
+    while lines:
+        if lines[0] == '':
+            lines.pop(0)
+        else:
+            blocks.append(parse_block(lines))
+
+    return blocks
+
+
+def first_key(lines):
+    """Returns a string representing the sort key of a block.
+
+    The sort key is the first YAML key we encounter, ignoring comments, and
+    stripping leading quotes.
+
+    >>> print(test)
+    # some comment
+    'foo': true
+    >>> first_key(test)
+    'foo'
+    """
+    for line in lines:
+        if line.startswith('#'):
+            continue
+        if any(line.startswith(quote) for quote in QUOTES):
+            return line[1:]
+        return line
+
+
+def main(argv=None):
+    parser = argparse.ArgumentParser()
+    parser.add_argument('filenames', nargs='*', help='Filenames to fix')
+    args = parser.parse_args(argv)
+
+    retval = 0
+
+    for filename in args.filenames:
+        with open(filename, 'r+') as f:
+            lines = [line.rstrip() for line in f.readlines()]
+            new_lines = sort(lines)
+
+            if lines != new_lines:
+                print("Fixing file `{filename}`".format(filename=filename))
+                f.seek(0)
+                f.write("\n".join(new_lines) + "\n")
+                f.truncate()
+                retval = 1
+
+    return retval
+
+
+if __name__ == '__main__':
+    exit(main())
diff --git a/pre_commit_hooks/tests_should_end_in_test.py b/pre_commit_hooks/tests_should_end_in_test.py
index 5f4cd08..5f9bbac 100644
--- a/pre_commit_hooks/tests_should_end_in_test.py
+++ b/pre_commit_hooks/tests_should_end_in_test.py
@@ -16,7 +16,7 @@
     args = parser.parse_args(argv)
 
     retcode = 0
-    test_name_pattern = 'test_.*.py' if args.django else '.*_test.py'
+    test_name_pattern = 'test.*.py' if args.django else '.*_test.py'
     for filename in args.filenames:
         base = basename(filename)
         if (
diff --git a/setup.py b/setup.py
index e82285d..90ef3c6 100644
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@
     name='pre_commit_hooks',
     description='Some out-of-the-box hooks for pre-commit.',
     url='https://github.com/pre-commit/pre-commit-hooks',
-    version='0.7.1',
+    version='0.9.1',
 
     author='Anthony Sottile',
     author_email='asottile@umich.edu',
@@ -39,6 +39,7 @@
             'check-byte-order-marker = pre_commit_hooks.check_byte_order_marker:main',
             'check-case-conflict = pre_commit_hooks.check_case_conflict:main',
             'check-docstring-first = pre_commit_hooks.check_docstring_first:main',
+            'check-executables-have-shebangs = pre_commit_hooks.check_executables_have_shebangs:main',
             'check-json = pre_commit_hooks.check_json:check_json',
             'check-merge-conflict = pre_commit_hooks.check_merge_conflict:detect_merge_conflict',
             'check-symlinks = pre_commit_hooks.check_symlinks:check_symlinks',
@@ -49,11 +50,14 @@
             'detect-private-key = pre_commit_hooks.detect_private_key:detect_private_key',
             'double-quote-string-fixer = pre_commit_hooks.string_fixer:main',
             'end-of-file-fixer = pre_commit_hooks.end_of_file_fixer:end_of_file_fixer',
+            'file-contents-sorter = pre_commit_hooks.file_contents_sorter:main',
             'fix-encoding-pragma = pre_commit_hooks.fix_encoding_pragma:main',
             'forbid-new-submodules = pre_commit_hooks.forbid_new_submodules:main',
             'name-tests-test = pre_commit_hooks.tests_should_end_in_test:validate_files',
+            'no-commit-to-branch = pre_commit_hooks.no_commit_to_branch:main',
             'pretty-format-json = pre_commit_hooks.pretty_format_json:pretty_format_json',
             'requirements-txt-fixer = pre_commit_hooks.requirements_txt_fixer:fix_requirements_txt',
+            'sort-simple-yaml = pre_commit_hooks.sort_simple_yaml:main',
             'trailing-whitespace-fixer = pre_commit_hooks.trailing_whitespace_fixer:fix_trailing_whitespace',
         ],
     },
diff --git a/testing/resources/aws_config_with_multiple_sections.ini b/testing/resources/aws_config_with_multiple_sections.ini
index ca6a8a3..2053b9a 100644
--- a/testing/resources/aws_config_with_multiple_sections.ini
+++ b/testing/resources/aws_config_with_multiple_sections.ini
@@ -9,4 +9,4 @@
 aws_access_key_id = AKIAJIMMINYCRICKET0A
 aws_secret_access_key = ixswosj8gz3wuik405jl9k3vdajsnxfhnpui38ez
 [test]
-aws_session_token = foo
\ No newline at end of file
+aws_session_token = foo
diff --git a/testing/resources/aws_config_with_secret_and_session_token.ini b/testing/resources/aws_config_with_secret_and_session_token.ini
index 4bd675d..4496765 100644
--- a/testing/resources/aws_config_with_secret_and_session_token.ini
+++ b/testing/resources/aws_config_with_secret_and_session_token.ini
@@ -2,4 +2,4 @@
 [production]
 aws_access_key_id = AKIAVOGONSVOGONS0042
 aws_secret_access_key = z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb
-aws_session_token = foo
\ No newline at end of file
+aws_session_token = foo
diff --git a/testing/resources/aws_config_with_session_token.ini b/testing/resources/aws_config_with_session_token.ini
index e07f2ac..b03f127 100644
--- a/testing/resources/aws_config_with_session_token.ini
+++ b/testing/resources/aws_config_with_session_token.ini
@@ -1,3 +1,3 @@
 # file with an AWS session token
 [production]
-aws_session_token = foo
\ No newline at end of file
+aws_session_token = foo
diff --git a/testing/resources/non_ascii_pretty_formatted_json.json b/testing/resources/non_ascii_pretty_formatted_json.json
new file mode 100644
index 0000000..05d0d00
--- /dev/null
+++ b/testing/resources/non_ascii_pretty_formatted_json.json
@@ -0,0 +1,10 @@
+{
+  "alist": [
+    2,
+    34,
+    234
+  ],
+  "blah": null,
+  "foo": "bar",
+  "non_ascii": "中文にほんご한국어"
+}
diff --git a/tests/check_executables_have_shebangs_test.py b/tests/check_executables_have_shebangs_test.py
new file mode 100644
index 0000000..0e28986
--- /dev/null
+++ b/tests/check_executables_have_shebangs_test.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import pytest
+
+from pre_commit_hooks.check_executables_have_shebangs import main
+
+
+@pytest.mark.parametrize('content', (
+    b'#!/bin/bash\nhello world\n',
+    b'#!/usr/bin/env python3.6',
+    b'#!python',
+    '#!☃'.encode('UTF-8'),
+))
+def test_has_shebang(content, tmpdir):
+    path = tmpdir.join('path')
+    path.write(content, 'wb')
+    assert main((path.strpath,)) == 0
+
+
+@pytest.mark.parametrize('content', (
+    b'',
+    b' #!python\n',
+    b'\n#!python\n',
+    b'python\n',
+    '☃'.encode('UTF-8'),
+
+))
+def test_bad_shebang(content, tmpdir, capsys):
+    path = tmpdir.join('path')
+    path.write(content, 'wb')
+    assert main((path.strpath,)) == 1
+    _, stderr = capsys.readouterr()
+    assert stderr.startswith('{}: marked executable but'.format(path.strpath))
diff --git a/tests/check_merge_conflict_test.py b/tests/check_merge_conflict_test.py
index f1528b2..5a2e82a 100644
--- a/tests/check_merge_conflict_test.py
+++ b/tests/check_merge_conflict_test.py
@@ -54,6 +54,14 @@
             '=======\n'
             'parent\n'
             '>>>>>>>'
+        ) or f1.startswith(
+            # .gitconfig with [pull] rebase = preserve causes a rebase which
+            # flips parent / child
+            '<<<<<<< HEAD\n'
+            'parent\n'
+            '=======\n'
+            'child\n'
+            '>>>>>>>'
         )
         assert os.path.exists(os.path.join('.git', 'MERGE_MSG'))
         yield
@@ -85,7 +93,7 @@
         repo2_f2.write('child\n')
         cmd_output('git', 'add', '--', repo2_f2.strpath)
         cmd_output('git', 'commit', '--no-gpg-sign', '-m', 'clone commit2')
-        cmd_output('git', 'pull', '--no-commit')
+        cmd_output('git', 'pull', '--no-commit', '--no-rebase')
         # We should end up in a pending merge
         assert repo2_f1.read() == 'parent\n'
         assert repo2_f2.read() == 'child\n'
diff --git a/tests/check_no_commit_to_branch_test.py b/tests/check_no_commit_to_branch_test.py
new file mode 100644
index 0000000..99af938
--- /dev/null
+++ b/tests/check_no_commit_to_branch_test.py
@@ -0,0 +1,47 @@
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+from pre_commit_hooks.no_commit_to_branch import is_on_branch
+from pre_commit_hooks.no_commit_to_branch import main
+from pre_commit_hooks.util import cmd_output
+
+
+def test_other_branch(temp_git_dir):
+    with temp_git_dir.as_cwd():
+        cmd_output('git', 'checkout', '-b', 'anotherbranch')
+        assert is_on_branch('master') is False
+
+
+def test_multi_branch(temp_git_dir):
+    with temp_git_dir.as_cwd():
+        cmd_output('git', 'checkout', '-b', 'another/branch')
+        assert is_on_branch('master') is False
+
+
+def test_multi_branch_fail(temp_git_dir):
+    with temp_git_dir.as_cwd():
+        cmd_output('git', 'checkout', '-b', 'another/branch')
+        assert is_on_branch('another/branch') is True
+
+
+def test_master_branch(temp_git_dir):
+    with temp_git_dir.as_cwd():
+        assert is_on_branch('master') is True
+
+
+def test_main_b_call(temp_git_dir):
+    with temp_git_dir.as_cwd():
+        cmd_output('git', 'checkout', '-b', 'other')
+        assert main(['-b', 'other']) == 1
+
+
+def test_main_branch_call(temp_git_dir):
+    with temp_git_dir.as_cwd():
+        cmd_output('git', 'checkout', '-b', 'other')
+        assert main(['--branch', 'other']) == 1
+
+
+def test_main_default_call(temp_git_dir):
+    with temp_git_dir.as_cwd():
+        cmd_output('git', 'checkout', '-b', 'anotherbranch')
+        assert main() == 0
diff --git a/tests/file_contents_sorter_test.py b/tests/file_contents_sorter_test.py
new file mode 100644
index 0000000..2c85c8a
--- /dev/null
+++ b/tests/file_contents_sorter_test.py
@@ -0,0 +1,33 @@
+import pytest
+
+from pre_commit_hooks.file_contents_sorter import FAIL
+from pre_commit_hooks.file_contents_sorter import main
+from pre_commit_hooks.file_contents_sorter import PASS
+
+
+@pytest.mark.parametrize(
+    ('input_s', 'expected_retval', 'output'),
+    (
+        (b'', FAIL, b'\n'),
+        (b'lonesome\n', PASS, b'lonesome\n'),
+        (b'missing_newline', FAIL, b'missing_newline\n'),
+        (b'newline\nmissing', FAIL, b'missing\nnewline\n'),
+        (b'missing\nnewline', FAIL, b'missing\nnewline\n'),
+        (b'alpha\nbeta\n', PASS, b'alpha\nbeta\n'),
+        (b'beta\nalpha\n', FAIL, b'alpha\nbeta\n'),
+        (b'C\nc\n', PASS, b'C\nc\n'),
+        (b'c\nC\n', FAIL, b'C\nc\n'),
+        (b'mag ical \n tre vor\n', FAIL, b' tre vor\nmag ical \n'),
+        (b'@\n-\n_\n#\n', FAIL, b'#\n-\n@\n_\n'),
+        (b'extra\n\n\nwhitespace\n', FAIL, b'extra\nwhitespace\n'),
+        (b'whitespace\n\n\nextra\n', FAIL, b'extra\nwhitespace\n'),
+    )
+)
+def test_integration(input_s, expected_retval, output, tmpdir):
+    path = tmpdir.join('file.txt')
+    path.write_binary(input_s)
+
+    output_retval = main([path.strpath])
+
+    assert path.read_binary() == output
+    assert output_retval == expected_retval
diff --git a/tests/meta_test.py b/tests/meta_test.py
index 202b3d7..29e06a8 100644
--- a/tests/meta_test.py
+++ b/tests/meta_test.py
@@ -1,7 +1,43 @@
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
 import io
 
+import yaml
 
-def test_hooks_yaml_same_contents():
-    legacy_contents = io.open('hooks.yaml').read()
-    contents = io.open('.pre-commit-hooks.yaml').read()
-    assert legacy_contents == contents
+
+def _assert_parseable_in_old_pre_commit(hooks):
+    for hook in hooks:
+        assert {'id', 'name', 'entry', 'files', 'language'} <= set(hook)
+
+
+def test_legacy_hooks():
+    with io.open('hooks.yaml') as legacy_file:
+        legacy = yaml.load(legacy_file.read())
+    with io.open('.pre-commit-hooks.yaml') as hooks_file:
+        hooks = yaml.load(hooks_file.read())
+
+    # The same set of hooks should be defined in both files
+    new_hook_ids = {hook['id'] for hook in hooks}
+    legacy_hook_ids = {hook['id'] for hook in legacy}
+    assert new_hook_ids == legacy_hook_ids
+
+    # Both files should be parseable by pre-commit<0.15.0
+    _assert_parseable_in_old_pre_commit(legacy)
+    _assert_parseable_in_old_pre_commit(hooks)
+
+    # The legacy file should force upgrading
+    for hook in legacy:
+        del hook['id']
+        assert hook == {
+            'language': 'system',
+            'name': 'upgrade-your-pre-commit-version',
+            'entry': 'upgrade-your-pre-commit-version',
+            'files': '',
+            'minimum_pre_commit_version': '0.15.0',
+        }
+
+    # Each hook should require a new version if it uses types
+    for hook in hooks:
+        if 'types' in hook:
+            assert hook['minimum_pre_commit_version'] == '0.15.0'
diff --git a/tests/pretty_format_json_test.py b/tests/pretty_format_json_test.py
index 7bfc31f..62e37f1 100644
--- a/tests/pretty_format_json_test.py
+++ b/tests/pretty_format_json_test.py
@@ -20,6 +20,7 @@
 @pytest.mark.parametrize(('filename', 'expected_retval'), (
     ('not_pretty_formatted_json.json', 1),
     ('unsorted_pretty_formatted_json.json', 1),
+    ('non_ascii_pretty_formatted_json.json', 1),
     ('pretty_formatted_json.json', 0),
 ))
 def test_pretty_format_json(filename, expected_retval):
@@ -30,6 +31,7 @@
 @pytest.mark.parametrize(('filename', 'expected_retval'), (
     ('not_pretty_formatted_json.json', 1),
     ('unsorted_pretty_formatted_json.json', 0),
+    ('non_ascii_pretty_formatted_json.json', 1),
     ('pretty_formatted_json.json', 0),
 ))
 def test_unsorted_pretty_format_json(filename, expected_retval):
@@ -40,6 +42,7 @@
 @pytest.mark.parametrize(('filename', 'expected_retval'), (
     ('not_pretty_formatted_json.json', 1),
     ('unsorted_pretty_formatted_json.json', 1),
+    ('non_ascii_pretty_formatted_json.json', 1),
     ('pretty_formatted_json.json', 1),
     ('tab_pretty_formatted_json.json', 0),
 ))
@@ -48,6 +51,11 @@
     assert ret == expected_retval
 
 
+def test_non_ascii_pretty_format_json():
+    ret = pretty_format_json(['--no-ensure-ascii', get_resource_path('non_ascii_pretty_formatted_json.json')])
+    assert ret == 0
+
+
 def test_autofix_pretty_format_json(tmpdir):
     srcfile = tmpdir.join('to_be_json_formatted.json')
     shutil.copyfile(
diff --git a/tests/requirements_txt_fixer_test.py b/tests/requirements_txt_fixer_test.py
index 1c590a5..3681cc6 100644
--- a/tests/requirements_txt_fixer_test.py
+++ b/tests/requirements_txt_fixer_test.py
@@ -1,31 +1,41 @@
 import pytest
 
+from pre_commit_hooks.requirements_txt_fixer import FAIL
 from pre_commit_hooks.requirements_txt_fixer import fix_requirements_txt
+from pre_commit_hooks.requirements_txt_fixer import PASS
 from pre_commit_hooks.requirements_txt_fixer import Requirement
 
-# Input, expected return value, expected output
-TESTS = (
-    (b'foo\nbar\n', 1, b'bar\nfoo\n'),
-    (b'bar\nfoo\n', 0, b'bar\nfoo\n'),
-    (b'#comment1\nfoo\n#comment2\nbar\n', 1, b'#comment2\nbar\n#comment1\nfoo\n'),
-    (b'#comment1\nbar\n#comment2\nfoo\n', 0, b'#comment1\nbar\n#comment2\nfoo\n'),
-    (b'#comment\n\nfoo\nbar\n', 1, b'#comment\n\nbar\nfoo\n'),
-    (b'#comment\n\nbar\nfoo\n', 0, b'#comment\n\nbar\nfoo\n'),
-    (b'\nfoo\nbar\n', 1, b'bar\n\nfoo\n'),
-    (b'\nbar\nfoo\n', 0, b'\nbar\nfoo\n'),
-    (b'pyramid==1\npyramid-foo==2\n', 0, b'pyramid==1\npyramid-foo==2\n'),
-    (b'ocflib\nDjango\nPyMySQL\n', 1, b'Django\nocflib\nPyMySQL\n'),
-    (b'-e git+ssh://git_url@tag#egg=ocflib\nDjango\nPyMySQL\n', 1, b'Django\n-e git+ssh://git_url@tag#egg=ocflib\nPyMySQL\n'),
+
+@pytest.mark.parametrize(
+    ('input_s', 'expected_retval', 'output'),
+    (
+        (b'', PASS, b''),
+        (b'\n', PASS, b'\n'),
+        (b'foo\nbar\n', FAIL, b'bar\nfoo\n'),
+        (b'bar\nfoo\n', PASS, b'bar\nfoo\n'),
+        (b'#comment1\nfoo\n#comment2\nbar\n', FAIL, b'#comment2\nbar\n#comment1\nfoo\n'),
+        (b'#comment1\nbar\n#comment2\nfoo\n', PASS, b'#comment1\nbar\n#comment2\nfoo\n'),
+        (b'#comment\n\nfoo\nbar\n', FAIL, b'#comment\n\nbar\nfoo\n'),
+        (b'#comment\n\nbar\nfoo\n', PASS, b'#comment\n\nbar\nfoo\n'),
+        (b'\nfoo\nbar\n', FAIL, b'bar\n\nfoo\n'),
+        (b'\nbar\nfoo\n', PASS, b'\nbar\nfoo\n'),
+        (b'pyramid==1\npyramid-foo==2\n', PASS, b'pyramid==1\npyramid-foo==2\n'),
+        (b'ocflib\nDjango\nPyMySQL\n', FAIL, b'Django\nocflib\nPyMySQL\n'),
+        (
+            b'-e git+ssh://git_url@tag#egg=ocflib\nDjango\nPyMySQL\n',
+            FAIL,
+            b'Django\n-e git+ssh://git_url@tag#egg=ocflib\nPyMySQL\n'
+        ),
+    )
 )
-
-
-@pytest.mark.parametrize(('input_s', 'expected_retval', 'output'), TESTS)
 def test_integration(input_s, expected_retval, output, tmpdir):
     path = tmpdir.join('file.txt')
     path.write_binary(input_s)
 
-    assert fix_requirements_txt([path.strpath]) == expected_retval
+    output_retval = fix_requirements_txt([path.strpath])
+
     assert path.read_binary() == output
+    assert output_retval == expected_retval
 
 
 def test_requirement_object():
diff --git a/tests/sort_simple_yaml_test.py b/tests/sort_simple_yaml_test.py
new file mode 100644
index 0000000..176d12f
--- /dev/null
+++ b/tests/sort_simple_yaml_test.py
@@ -0,0 +1,120 @@
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import os
+
+import pytest
+
+from pre_commit_hooks.sort_simple_yaml import first_key
+from pre_commit_hooks.sort_simple_yaml import main
+from pre_commit_hooks.sort_simple_yaml import parse_block
+from pre_commit_hooks.sort_simple_yaml import parse_blocks
+from pre_commit_hooks.sort_simple_yaml import sort
+
+RETVAL_GOOD = 0
+RETVAL_BAD = 1
+TEST_SORTS = [
+    (
+        ['c: true', '', 'b: 42', 'a: 19'],
+        ['b: 42', 'a: 19', '', 'c: true'],
+        RETVAL_BAD,
+    ),
+
+    (
+        ['# i am', '# a header', '', 'c: true', '', 'b: 42', 'a: 19'],
+        ['# i am', '# a header', '', 'b: 42', 'a: 19', '', 'c: true'],
+        RETVAL_BAD,
+    ),
+
+    (
+        ['# i am', '# a header', '', 'already: sorted', '', 'yup: i am'],
+        ['# i am', '# a header', '', 'already: sorted', '', 'yup: i am'],
+        RETVAL_GOOD,
+    ),
+
+    (
+        ['# i am', '# a header'],
+        ['# i am', '# a header'],
+        RETVAL_GOOD,
+    ),
+]
+
+
+@pytest.mark.parametrize('bad_lines,good_lines,retval', TEST_SORTS)
+def test_integration_good_bad_lines(tmpdir, bad_lines, good_lines, retval):
+    file_path = os.path.join(tmpdir.strpath, 'foo.yaml')
+
+    with open(file_path, 'w') as f:
+        f.write("\n".join(bad_lines) + "\n")
+
+    assert main([file_path]) == retval
+
+    with open(file_path, 'r') as f:
+        assert [line.rstrip() for line in f.readlines()] == good_lines
+
+
+def test_parse_header():
+    lines = ['# some header', '# is here', '', 'this is not a header']
+    assert parse_block(lines, header=True) == ['# some header', '# is here']
+    assert lines == ['', 'this is not a header']
+
+    lines = ['this is not a header']
+    assert parse_block(lines, header=True) == []
+    assert lines == ['this is not a header']
+
+
+def test_parse_block():
+    # a normal block
+    lines = ['a: 42', 'b: 17', '', 'c: 19']
+    assert parse_block(lines) == ['a: 42', 'b: 17']
+    assert lines == ['', 'c: 19']
+
+    # a block at the end
+    lines = ['c: 19']
+    assert parse_block(lines) == ['c: 19']
+    assert lines == []
+
+    # no block
+    lines = []
+    assert parse_block(lines) == []
+    assert lines == []
+
+
+def test_parse_blocks():
+    # normal blocks
+    lines = ['a: 42', 'b: 17', '', 'c: 19']
+    assert parse_blocks(lines) == [['a: 42', 'b: 17'], ['c: 19']]
+    assert lines == []
+
+    # a single block
+    lines = ['a: 42', 'b: 17']
+    assert parse_blocks(lines) == [['a: 42', 'b: 17']]
+    assert lines == []
+
+    # no blocks
+    lines = []
+    assert parse_blocks(lines) == []
+    assert lines == []
+
+
+def test_first_key():
+    # first line
+    lines = ['a: 42', 'b: 17', '', 'c: 19']
+    assert first_key(lines) == 'a: 42'
+
+    # second line
+    lines = ['# some comment', 'a: 42', 'b: 17', '', 'c: 19']
+    assert first_key(lines) == 'a: 42'
+
+    # second line with quotes
+    lines = ['# some comment', '"a": 42', 'b: 17', '', 'c: 19']
+    assert first_key(lines) == 'a": 42'
+
+    # no lines
+    lines = []
+    assert first_key(lines) is None
+
+
+@pytest.mark.parametrize('bad_lines,good_lines,_', TEST_SORTS)
+def test_sort(bad_lines, good_lines, _):
+    assert sort(bad_lines) == good_lines
diff --git a/tests/tests_should_end_in_test_test.py b/tests/tests_should_end_in_test_test.py
index a7aaf52..dc686a5 100644
--- a/tests/tests_should_end_in_test_test.py
+++ b/tests/tests_should_end_in_test_test.py
@@ -12,7 +12,7 @@
 
 
 def test_validate_files_django_all_pass():
-    ret = validate_files(['--django', 'test_foo.py', 'test_bar.py', 'tests/test_baz.py'])
+    ret = validate_files(['--django', 'tests.py', 'test_foo.py', 'test_bar.py', 'tests/test_baz.py'])
     assert ret == 0
 
 
diff --git a/tox.ini b/tox.ini
index bf2eba2..9dd4c2f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,7 @@
 [tox]
 project = pre_commit_hooks
 # These should match the travis env list
-envlist = py27,py34,py35,pypy
+envlist = py27,py35,py36,pypy
 
 [testenv]
 deps = -rrequirements-dev.txt