Skip to content

Commit 950451e

Browse files
authored
Merge pull request #2725 from mgaligniana/issue-2582
Add pre-rebase hook support
2 parents f2661bf + f39154f commit 950451e

File tree

9 files changed

+114
-0
lines changed

9 files changed

+114
-0
lines changed

pre_commit/clientlib.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
'pre-commit',
3131
'pre-merge-commit',
3232
'pre-push',
33+
'pre-rebase',
3334
'prepare-commit-msg',
3435
)
3536
# `manual` is not invoked by any installed git hook. See #719

pre_commit/commands/hook_impl.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ def _ns(
7373
local_branch: str | None = None,
7474
from_ref: str | None = None,
7575
to_ref: str | None = None,
76+
pre_rebase_upstream: str | None = None,
77+
pre_rebase_branch: str | None = None,
7678
remote_name: str | None = None,
7779
remote_url: str | None = None,
7880
commit_msg_filename: str | None = None,
@@ -89,6 +91,8 @@ def _ns(
8991
local_branch=local_branch,
9092
from_ref=from_ref,
9193
to_ref=to_ref,
94+
pre_rebase_upstream=pre_rebase_upstream,
95+
pre_rebase_branch=pre_rebase_branch,
9296
remote_name=remote_name,
9397
remote_url=remote_url,
9498
commit_msg_filename=commit_msg_filename,
@@ -185,6 +189,12 @@ def _check_args_length(hook_type: str, args: Sequence[str]) -> None:
185189
f'hook-impl for {hook_type} expected 1, 2, or 3 arguments '
186190
f'but got {len(args)}: {args}',
187191
)
192+
elif hook_type == 'pre-rebase':
193+
if len(args) < 1 or len(args) > 2:
194+
raise SystemExit(
195+
f'hook-impl for {hook_type} expected 1 or 2 arguments '
196+
f'but got {len(args)}: {args}',
197+
)
188198
elif hook_type in _EXPECTED_ARG_LENGTH_BY_HOOK:
189199
expected = _EXPECTED_ARG_LENGTH_BY_HOOK[hook_type]
190200
if len(args) != expected:
@@ -231,6 +241,13 @@ def _run_ns(
231241
return _ns(hook_type, color, is_squash_merge=args[0])
232242
elif hook_type == 'post-rewrite':
233243
return _ns(hook_type, color, rewrite_command=args[0])
244+
elif hook_type == 'pre-rebase' and len(args) == 1:
245+
return _ns(hook_type, color, pre_rebase_upstream=args[0])
246+
elif hook_type == 'pre-rebase' and len(args) == 2:
247+
return _ns(
248+
hook_type, color, pre_rebase_upstream=args[0],
249+
pre_rebase_branch=args[1],
250+
)
234251
else:
235252
raise AssertionError(f'unexpected hook type: {hook_type}')
236253

pre_commit/commands/run.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ def _all_filenames(args: argparse.Namespace) -> Collection[str]:
254254
# these hooks do not operate on files
255255
if args.hook_stage in {
256256
'post-checkout', 'post-commit', 'post-merge', 'post-rewrite',
257+
'pre-rebase',
257258
}:
258259
return ()
259260
elif args.hook_stage in {'prepare-commit-msg', 'commit-msg'}:
@@ -389,6 +390,10 @@ def run(
389390
environ['PRE_COMMIT_FROM_REF'] = args.from_ref
390391
environ['PRE_COMMIT_TO_REF'] = args.to_ref
391392

393+
if args.pre_rebase_upstream and args.pre_rebase_branch:
394+
environ['PRE_COMMIT_PRE_REBASE_UPSTREAM'] = args.pre_rebase_upstream
395+
environ['PRE_COMMIT_PRE_REBASE_BRANCH'] = args.pre_rebase_branch
396+
392397
if (
393398
args.remote_name and args.remote_url and
394399
args.remote_branch and args.local_branch

pre_commit/main.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,17 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None:
107107
'now checked out.'
108108
),
109109
)
110+
parser.add_argument(
111+
'--pre-rebase-upstream', help=(
112+
'The upstream from which the series was forked.'
113+
),
114+
)
115+
parser.add_argument(
116+
'--pre-rebase-branch', help=(
117+
'The branch being rebased, and is not set when '
118+
'rebasing the current branch.'
119+
),
120+
)
110121
parser.add_argument(
111122
'--commit-msg-filename',
112123
help='Filename to check when running during `commit-msg`',

testing/util.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ def run_opts(
4444
local_branch='',
4545
from_ref='',
4646
to_ref='',
47+
pre_rebase_upstream='',
48+
pre_rebase_branch='',
4749
remote_name='',
4850
remote_url='',
4951
hook_stage='pre-commit',
@@ -67,6 +69,8 @@ def run_opts(
6769
local_branch=local_branch,
6870
from_ref=from_ref,
6971
to_ref=to_ref,
72+
pre_rebase_upstream=pre_rebase_upstream,
73+
pre_rebase_branch=pre_rebase_branch,
7074
remote_name=remote_name,
7175
remote_url=remote_url,
7276
hook_stage=hook_stage,

tests/commands/hook_impl_test.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ def call(*_, **__):
100100
('commit-msg', ['.git/COMMIT_EDITMSG']),
101101
('post-commit', []),
102102
('post-merge', ['1']),
103+
('pre-rebase', ['main', 'topic']),
104+
('pre-rebase', ['main']),
103105
('post-checkout', ['old_head', 'new_head', '1']),
104106
('post-rewrite', ['amend']),
105107
# multiple choices for commit-editmsg
@@ -139,13 +141,36 @@ def test_check_args_length_prepare_commit_msg_error():
139141
)
140142

141143

144+
def test_check_args_length_pre_rebase_error():
145+
with pytest.raises(SystemExit) as excinfo:
146+
hook_impl._check_args_length('pre-rebase', [])
147+
msg, = excinfo.value.args
148+
assert msg == 'hook-impl for pre-rebase expected 1 or 2 arguments but got 0: []' # noqa: E501
149+
150+
142151
def test_run_ns_pre_commit():
143152
ns = hook_impl._run_ns('pre-commit', True, (), b'')
144153
assert ns is not None
145154
assert ns.hook_stage == 'pre-commit'
146155
assert ns.color is True
147156

148157

158+
def test_run_ns_pre_rebase():
159+
ns = hook_impl._run_ns('pre-rebase', True, ('main', 'topic'), b'')
160+
assert ns is not None
161+
assert ns.hook_stage == 'pre-rebase'
162+
assert ns.color is True
163+
assert ns.pre_rebase_upstream == 'main'
164+
assert ns.pre_rebase_branch == 'topic'
165+
166+
ns = hook_impl._run_ns('pre-rebase', True, ('main',), b'')
167+
assert ns is not None
168+
assert ns.hook_stage == 'pre-rebase'
169+
assert ns.color is True
170+
assert ns.pre_rebase_upstream == 'main'
171+
assert ns.pre_rebase_branch is None
172+
173+
149174
def test_run_ns_commit_msg():
150175
ns = hook_impl._run_ns('commit-msg', False, ('.git/COMMIT_MSG',), b'')
151176
assert ns is not None

tests/commands/install_uninstall_test.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,46 @@ def test_post_merge_integration(tempdir_factory, store):
810810
assert os.path.exists('post-merge.tmp')
811811

812812

813+
def test_pre_rebase_integration(tempdir_factory, store):
814+
path = git_dir(tempdir_factory)
815+
config = {
816+
'repos': [
817+
{
818+
'repo': 'local',
819+
'hooks': [{
820+
'id': 'pre-rebase',
821+
'name': 'Pre rebase',
822+
'entry': 'touch pre-rebase.tmp',
823+
'language': 'system',
824+
'always_run': True,
825+
'verbose': True,
826+
'stages': ['pre-rebase'],
827+
}],
828+
},
829+
],
830+
}
831+
write_config(path, config)
832+
with cwd(path):
833+
install(C.CONFIG_FILE, store, hook_types=['pre-rebase'])
834+
open('foo', 'a').close()
835+
cmd_output('git', 'add', '.')
836+
git_commit()
837+
838+
cmd_output('git', 'checkout', '-b', 'branch')
839+
open('bar', 'a').close()
840+
cmd_output('git', 'add', '.')
841+
git_commit()
842+
843+
cmd_output('git', 'checkout', 'master')
844+
open('baz', 'a').close()
845+
cmd_output('git', 'add', '.')
846+
git_commit()
847+
848+
cmd_output('git', 'checkout', 'branch')
849+
cmd_output('git', 'rebase', 'master', 'branch')
850+
assert os.path.exists('pre-rebase.tmp')
851+
852+
813853
def test_post_rewrite_integration(tempdir_factory, store):
814854
path = git_dir(tempdir_factory)
815855
config = {

tests/commands/run_test.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,16 @@ def test_merge_conflict_resolved(cap_out, store, in_merge_conflict):
563563
assert msg in printed
564564

565565

566+
def test_rebase(cap_out, store, repo_with_passing_hook):
567+
args = run_opts(pre_rebase_upstream='master', pre_rebase_branch='topic')
568+
environ: MutableMapping[str, str] = {}
569+
ret, printed = _do_run(
570+
cap_out, store, repo_with_passing_hook, args, environ,
571+
)
572+
assert environ['PRE_COMMIT_PRE_REBASE_UPSTREAM'] == 'master'
573+
assert environ['PRE_COMMIT_PRE_REBASE_BRANCH'] == 'topic'
574+
575+
566576
@pytest.mark.parametrize(
567577
('hooks', 'expected'),
568578
(

tests/repository_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@ def test_manifest_hooks(tempdir_factory, store):
522522
'pre-commit',
523523
'pre-merge-commit',
524524
'pre-push',
525+
'pre-rebase',
525526
'prepare-commit-msg',
526527
'manual',
527528
],

0 commit comments

Comments
 (0)