From 54485588e53262ab4cee21720e0a4a58f934df49 Mon Sep 17 00:00:00 2001 From: Vitalii Budnik Date: Mon, 23 Dec 2024 17:30:15 +0300 Subject: [PATCH] fix: failing post-checkout on single file checkout --- pre_commit/commands/run.py | 16 ++++++++++++++-- tests/commands/run_test.py | 39 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index 793adbdb..4f1dd8f7 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -344,8 +344,20 @@ def run( # Check if we have unresolved merge conflict files and fail fast. if stash and _has_unmerged_paths(): - logger.error('Unmerged files. Resolve before committing.') - return 1 + # Do not fail on unmerged files if this is a post-checkout hooks. + # This handles cases when there are unmerged paths and: + # * resolving conflict with `git checkout --ours/--theirs -- ` + # * or checking out single files with `git checkout -- `. + if args.hook_stage == 'post-checkout': + output.write_line( + f'Skipping `{args.hook_stage}` hooks since ' + f'it\'s a file checkout or same head ref', + ) + return 0 + else: + logger.error('Unmerged files. Resolve before committing.') + return 1 + if bool(args.from_ref) != bool(args.to_ref): logger.error('Specify both --from-ref and --to-ref.') return 1 diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index 50a20f37..22034ea2 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -536,6 +536,22 @@ def test_merge_conflict(cap_out, store, in_merge_conflict): assert b'Unmerged files. Resolve before committing.' in printed +def test_merge_conflict_post_checkout(cap_out, store, in_merge_conflict): + ret, printed = _do_run( + cap_out, + store, + in_merge_conflict, + run_opts(hook_stage='post-checkout'), + ) + assert ret == 0 + assert b'Unmerged files. Resolve before committing.' not in printed + msg = ( + b'Skipping `post-checkout` hooks since ' + b'it\'s a file checkout or same head ref' + ) + assert msg in printed + + def test_files_during_merge_conflict(cap_out, store, in_merge_conflict): opts = run_opts(files=['placeholder']) ret, printed = _do_run(cap_out, store, in_merge_conflict, opts) @@ -554,6 +570,29 @@ def test_merge_conflict_modified(cap_out, store, in_merge_conflict): assert b'Unmerged files. Resolve before committing.' in printed +def test_merge_conflict_modified_post_checkout( + cap_out, store, in_merge_conflict +): + # Touch another file so we have unstaged non-conflicting things + assert os.path.exists('placeholder') + with open('placeholder', 'w') as placeholder_file: + placeholder_file.write('bar\nbaz\n') + + ret, printed = _do_run( + cap_out, + store, + in_merge_conflict, + run_opts(hook_stage='post-checkout'), + ) + assert ret == 0 + assert b'Unmerged files. Resolve before committing.' not in printed + msg = ( + b'Skipping `post-checkout` hooks since ' + b'it\'s a file checkout or same head ref' + ) + assert msg in printed + + def test_merge_conflict_resolved(cap_out, store, in_merge_conflict): cmd_output('git', 'add', '.') ret, printed = _do_run(cap_out, store, in_merge_conflict, run_opts())