Repository: pre-commit/pre-commit

Issue:
Pre-push hook checks only when pushing to certain remote
I have two remotes configured in my repository (`origin` and `upstream`) and I want my pre-push hook to fire and do the configured checks only when we push to `upstream`, bypassing all checks when we push to remote `origin`. Is this possible without using git `--no-verify` option?
I was checking for ways to obtain the remote from `pre-commit`as a parameter and pass it to my local hook to bypass the checks in case remote doesn't match `upstream` but I haven't been very successful. Any ideas on how to do this?

Thank you so much for all the great work you are doing with this project.

Discussion:
dmbarreiro: Comes from issue #1274 
asottile: > Comes from issue #1274

note that if you put "Closes #1274" in your PR description this'll get automatically linked *and* github will auto-close the issue upon merging!
asottile: I ran the hooks and moved the test (the `run` tests do some isolated `environ` management so it doesn't get forwarded to the hook executables as a normal run would)

Patch:
diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py
index 6690bdd..95f8ab4 100644
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -312,6 +312,10 @@ def run(
         environ['PRE_COMMIT_ORIGIN'] = args.origin
         environ['PRE_COMMIT_SOURCE'] = args.source
 
+    if args.remote_name and args.remote_url:
+        environ['PRE_COMMIT_REMOTE_NAME'] = args.remote_name
+        environ['PRE_COMMIT_REMOTE_URL'] = args.remote_url
+
     with contextlib.ExitStack() as exit_stack:
         if stash:
             exit_stack.enter_context(staged_files_only(store.directory))
diff --git a/pre_commit/main.py b/pre_commit/main.py
index d96b35f..e65d8ae 100644
--- a/pre_commit/main.py
+++ b/pre_commit/main.py
@@ -101,6 +101,10 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None:
         '--commit-msg-filename',
         help='Filename to check when running during `commit-msg`',
     )
+    parser.add_argument(
+        '--remote-name', help='Remote name used by `git push`.',
+    )
+    parser.add_argument('--remote-url', help='Remote url used by `git push`.')
     parser.add_argument(
         '--hook-stage', choices=C.STAGES, default='commit',
         help='The stage during which the hook is fired.  One of %(choices)s',
diff --git a/pre_commit/resources/hook-tmpl b/pre_commit/resources/hook-tmpl
index 213d16e..573335a 100755
--- a/pre_commit/resources/hook-tmpl
+++ b/pre_commit/resources/hook-tmpl
@@ -120,7 +120,8 @@ def _rev_exists(rev: str) -> bool:
 
 
 def _pre_push(stdin: bytes) -> Tuple[str, ...]:
-    remote = sys.argv[1]
+    remote_name = sys.argv[1]
+    remote_url = sys.argv[2]
 
     opts: Tuple[str, ...] = ()
     for line in stdin.decode().splitlines():
@@ -133,7 +134,7 @@ def _pre_push(stdin: bytes) -> Tuple[str, ...]:
             # ancestors not found in remote
             ancestors = subprocess.check_output((
                 'git', 'rev-list', local_sha, '--topo-order', '--reverse',
-                '--not', f'--remotes={remote}',
+                '--not', f'--remotes={remote_name}',
             )).decode().strip()
             if not ancestors:
                 continue
@@ -150,7 +151,9 @@ def _pre_push(stdin: bytes) -> Tuple[str, ...]:
                     opts = ('--origin', local_sha, '--source', source)
 
     if opts:
-        return opts
+        return (
+            *opts, '--remote-name', remote_name, '--remote-url', remote_url,
+        )
     else:
         # An attempt to push an empty changeset
         raise EarlyExit()


Test patch:
diff --git a/testing/util.py b/testing/util.py
index 0c2cc6a..ce3206e 100644
--- a/testing/util.py
+++ b/testing/util.py
@@ -67,6 +67,8 @@ def run_opts(
         hook=None,
         origin='',
         source='',
+        remote_name='',
+        remote_url='',
         hook_stage='commit',
         show_diff_on_failure=False,
         commit_msg_filename='',
@@ -81,6 +83,8 @@ def run_opts(
         hook=hook,
         origin=origin,
         source=source,
+        remote_name=remote_name,
+        remote_url=remote_url,
         hook_stage=hook_stage,
         show_diff_on_failure=show_diff_on_failure,
         commit_msg_filename=commit_msg_filename,
diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py
index 562293d..984ae74 100644
--- a/tests/commands/install_uninstall_test.py
+++ b/tests/commands/install_uninstall_test.py
@@ -15,6 +15,7 @@ from pre_commit.parse_shebang import find_executable
 from pre_commit.util import cmd_output
 from pre_commit.util import make_executable
 from pre_commit.util import resource_text
+from testing.fixtures import add_config_to_repo
 from testing.fixtures import git_dir
 from testing.fixtures import make_consuming_repo
 from testing.fixtures import remove_config_from_repo
@@ -512,9 +513,9 @@ def test_installed_from_venv(tempdir_factory, store):
         assert NORMAL_PRE_COMMIT_RUN.match(output)
 
 
-def _get_push_output(tempdir_factory, opts=()):
+def _get_push_output(tempdir_factory, remote='origin', opts=()):
     return cmd_output_mocked_pre_commit_home(
-        'git', 'push', 'origin', 'HEAD:new_branch', *opts,
+        'git', 'push', remote, 'HEAD:new_branch', *opts,
         tempdir_factory=tempdir_factory,
         retcode=None,
     )[:2]
@@ -589,6 +590,33 @@ def test_pre_push_new_upstream(tempdir_factory, store):
         assert 'Passed' in output
 
 
+def test_pre_push_environment_variables(tempdir_factory, store):
+    config = {
+        'repo': 'local',
+        'hooks': [
+            {
+                'id': 'print-remote-info',
+                'name': 'print remote info',
+                'entry': 'bash -c "echo remote: $PRE_COMMIT_REMOTE_NAME"',
+                'language': 'system',
+                'verbose': True,
+            },
+        ],
+    }
+
+    upstream = git_dir(tempdir_factory)
+    clone = tempdir_factory.get()
+    cmd_output('git', 'clone', upstream, clone)
+    add_config_to_repo(clone, config)
+    with cwd(clone):
+        install(C.CONFIG_FILE, store, hook_types=['pre-push'])
+
+        cmd_output('git', 'remote', 'rename', 'origin', 'origin2')
+        retc, output = _get_push_output(tempdir_factory, remote='origin2')
+        assert retc == 0
+        assert '\nremote: origin2\n' in output
+
+
 def test_pre_push_integration_empty_push(tempdir_factory, store):
     upstream = make_consuming_repo(tempdir_factory, 'script_hooks_repo')
     path = tempdir_factory.get()
diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py
index d2e2f23..87eef2e 100644
--- a/tests/commands/run_test.py
+++ b/tests/commands/run_test.py
@@ -456,8 +456,11 @@ def test_origin_source_error_msg_error(
     assert b'Specify both --origin and --source.' in printed
 
 
-def test_origin_source_both_ok(cap_out, store, repo_with_passing_hook):
-    args = run_opts(origin='master', source='master')
+def test_all_push_options_ok(cap_out, store, repo_with_passing_hook):
+    args = run_opts(
+        origin='master', source='master',
+        remote_name='origin', remote_url='https://example.com/repo',
+    )
     ret, printed = _do_run(cap_out, store, repo_with_passing_hook, args)
     assert ret == 0
     assert b'Specify both --origin and --source.' not in printed