pygittools 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygittools-0.1.0/LICENSE +21 -0
- pygittools-0.1.0/PKG-INFO +27 -0
- pygittools-0.1.0/README.md +16 -0
- pygittools-0.1.0/__init__.py +10 -0
- pygittools-0.1.0/__meta__.py +9 -0
- pygittools-0.1.0/hook_samples/README.md +44 -0
- pygittools-0.1.0/hook_samples/commit-msg.pattern +24 -0
- pygittools-0.1.0/hook_samples/post-commit.notify +24 -0
- pygittools-0.1.0/hook_samples/post-commit.push-remotes +23 -0
- pygittools-0.1.0/hook_samples/post-receive.notify +27 -0
- pygittools-0.1.0/hook_samples/pre-commit.ruff +18 -0
- pygittools-0.1.0/hook_samples/pre-receive.protected-pattern +37 -0
- pygittools-0.1.0/hooks.py +336 -0
- pygittools-0.1.0/hooks_notify.py +101 -0
- pygittools-0.1.0/hooks_patterns.py +134 -0
- pygittools-0.1.0/hooks_patterns_test.py +91 -0
- pygittools-0.1.0/hooks_push.py +77 -0
- pygittools-0.1.0/hooks_push_test.py +82 -0
- pygittools-0.1.0/hooks_ruff.py +104 -0
- pygittools-0.1.0/merge.py +247 -0
- pygittools-0.1.0/merge_test.py +165 -0
- pygittools-0.1.0/pygittools.egg-info/PKG-INFO +27 -0
- pygittools-0.1.0/pygittools.egg-info/SOURCES.txt +48 -0
- pygittools-0.1.0/pygittools.egg-info/dependency_links.txt +1 -0
- pygittools-0.1.0/pygittools.egg-info/requires.txt +1 -0
- pygittools-0.1.0/pygittools.egg-info/top_level.txt +1 -0
- pygittools-0.1.0/pyproject.toml +24 -0
- pygittools-0.1.0/setup.cfg +4 -0
- pygittools-0.1.0/tasks.py +333 -0
- pygittools-0.1.0/tasks_test.py +203 -0
pygittools-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Will Bowers
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pygittools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Hooks and task storage in Git
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: pygit2>=1.12.0
|
|
10
|
+
Dynamic: license-file
|
|
11
|
+
|
|
12
|
+
# PyGitTools
|
|
13
|
+
|
|
14
|
+
Authentication, hooks, and pygit2 workflows used by PyGitWeb. Can be used headless for many purposes.
|
|
15
|
+
|
|
16
|
+
## Hook framework
|
|
17
|
+
|
|
18
|
+
`pygittools.hooks` provides a thin `Hook` base class plus one subclass per git hook type
|
|
19
|
+
(client- and server-side). Receive-pack hooks (`PreReceive`, `PostReceive`) parse their
|
|
20
|
+
ref-update lines via `parse_ref_updates(stdin)`.
|
|
21
|
+
|
|
22
|
+
`pygittools.hooks_notify` adds `PreReceiveNotify` / `PostReceiveNotify`: minimal hook
|
|
23
|
+
variants that POST `?project=<name>` to a pygitweb `/_internal/notify` endpoint so its
|
|
24
|
+
long-poll `updates=true` subscribers wake up immediately on each push. Notification
|
|
25
|
+
failures never reject the push.
|
|
26
|
+
|
|
27
|
+
See `hook_samples/` for ready-to-use scripts.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# PyGitTools
|
|
2
|
+
|
|
3
|
+
Authentication, hooks, and pygit2 workflows used by PyGitWeb. Can be used headless for many purposes.
|
|
4
|
+
|
|
5
|
+
## Hook framework
|
|
6
|
+
|
|
7
|
+
`pygittools.hooks` provides a thin `Hook` base class plus one subclass per git hook type
|
|
8
|
+
(client- and server-side). Receive-pack hooks (`PreReceive`, `PostReceive`) parse their
|
|
9
|
+
ref-update lines via `parse_ref_updates(stdin)`.
|
|
10
|
+
|
|
11
|
+
`pygittools.hooks_notify` adds `PreReceiveNotify` / `PostReceiveNotify`: minimal hook
|
|
12
|
+
variants that POST `?project=<name>` to a pygitweb `/_internal/notify` endpoint so its
|
|
13
|
+
long-poll `updates=true` subscribers wake up immediately on each push. Notification
|
|
14
|
+
failures never reject the push.
|
|
15
|
+
|
|
16
|
+
See `hook_samples/` for ready-to-use scripts.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import pygittools.__meta__
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Python hooks and metadata storage in Git
|
|
5
|
+
"""
|
|
6
|
+
__version__ = pygittools.__meta__.__version__
|
|
7
|
+
__author__ = pygittools.__meta__.__author__
|
|
8
|
+
__description__ = pygittools.__meta__.__description__
|
|
9
|
+
__url__ = pygittools.__meta__.__url__
|
|
10
|
+
__license__ = pygittools.__meta__.__license__
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Metadata for PyGitTools - this is the canonical source of all information below.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "0.1.0"
|
|
6
|
+
__author__ = "Will Bowers"
|
|
7
|
+
__license__ = "MIT"
|
|
8
|
+
__description__ = "Python hooks and metadata storage in Git"
|
|
9
|
+
__url__ = "https://pygitweb.com"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Sample Hooks
|
|
2
|
+
|
|
3
|
+
Place these as desired in `.git/hooks/` WITHOUT the suffix.
|
|
4
|
+
|
|
5
|
+
You will just need UV with pygittools in your virtual environment.
|
|
6
|
+
|
|
7
|
+
## post-receive.notify and post-commit.notify
|
|
8
|
+
|
|
9
|
+
Wake pygitweb's long-poll subscribers (`updates=true`) on every push (server-side) and
|
|
10
|
+
every local commit (client-side). They share two environment variables:
|
|
11
|
+
|
|
12
|
+
- `PYGITWEB_NOTIFY_URL` — the running server's notify endpoint (default
|
|
13
|
+
`http://127.0.0.1:8000/_internal/notify`)
|
|
14
|
+
- `PYGITWEB_PROJECT` — project path as known to pygitweb (default: the bare repo's
|
|
15
|
+
directory name with `.git` stripped, or for non-bare clones the working-tree directory
|
|
16
|
+
name)
|
|
17
|
+
|
|
18
|
+
The pygitweb summary page can install both at once via the **Hooks** row (the `update`
|
|
19
|
+
bundle installs `post-commit.notify` and `post-receive.notify` together).
|
|
20
|
+
|
|
21
|
+
## pre-receive.protected-pattern
|
|
22
|
+
|
|
23
|
+
Reject pushes to protected refs when any newly introduced non-merge commit message does
|
|
24
|
+
not match a pattern. Merge commits are skipped by default; set `PYGITWEB_REJECT_MERGE_COMMITS=1`
|
|
25
|
+
to refuse merge commits and require squash or rebase instead.
|
|
26
|
+
|
|
27
|
+
Environment variables:
|
|
28
|
+
|
|
29
|
+
- `PYGITWEB_PROTECTED_REF_PATTERN` — refs to protect (default `^refs/heads/(main|master)$`)
|
|
30
|
+
- `PYGITWEB_COMMIT_MSG_PATTERN` — message regex (default `^(\S+): (.+)`)
|
|
31
|
+
- `PYGITWEB_REJECT_MERGE_COMMITS` — set to `1`/`true`/`yes` to reject merge commits
|
|
32
|
+
|
|
33
|
+
## post-commit.push-remotes
|
|
34
|
+
|
|
35
|
+
After each local commit, force-with-lease push the current branch to every configured
|
|
36
|
+
remote when the branch name matches a pattern. Uses the git CLI (`git push
|
|
37
|
+
--force-with-lease`), so SSH remotes honor `~/.ssh/config` like a normal push. Push
|
|
38
|
+
failures are logged on stderr but do not affect the commit (post-commit hooks cannot undo
|
|
39
|
+
a commit).
|
|
40
|
+
|
|
41
|
+
Environment variables:
|
|
42
|
+
|
|
43
|
+
- `PYGITWEB_PUSH_BRANCH_PATTERN` — branch name regex (default `^wip/`)
|
|
44
|
+
- `GIT` — git executable (default: `git` on `PATH`)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env -S uv run python
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__VERSION__ = "1"
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import pygit2
|
|
9
|
+
from pygittools.hooks import HookResult
|
|
10
|
+
from pygittools.hooks_patterns import CommitMsgPattern
|
|
11
|
+
|
|
12
|
+
PATTERN = r"^(\S+): (.+)"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main() -> int:
|
|
16
|
+
repo = pygit2.Repository(".")
|
|
17
|
+
result = CommitMsgPattern(repo, PATTERN).run(sys.argv[1])
|
|
18
|
+
if result.value == HookResult.FAILURE:
|
|
19
|
+
print("Commit message does not match pattern:", PATTERN, file=sys.stderr)
|
|
20
|
+
return int(result.value)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env -S uv run python
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__VERSION__ = "1"
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
import pygit2
|
|
10
|
+
|
|
11
|
+
from pygittools.hooks_notify import PostCommitNotify
|
|
12
|
+
|
|
13
|
+
NOTIFY_URL = os.environ.get("PYGITWEB_NOTIFY_URL", "http://127.0.0.1:8000/_internal/notify")
|
|
14
|
+
PROJECT = os.environ.get("PYGITWEB_PROJECT") or os.path.basename(os.getcwd()).removesuffix(".git")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main() -> int:
|
|
18
|
+
repo = pygit2.Repository(".")
|
|
19
|
+
result = PostCommitNotify(repo, NOTIFY_URL, PROJECT).run()
|
|
20
|
+
return int(result.value)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env -S uv run python
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__VERSION__ = "1"
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
import pygit2
|
|
10
|
+
|
|
11
|
+
from pygittools.hooks_push import PostCommitPushRemotes
|
|
12
|
+
|
|
13
|
+
BRANCH_PATTERN = os.environ.get("PYGITWEB_PUSH_BRANCH_PATTERN", r"^wip/")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main() -> int:
|
|
17
|
+
repo = pygit2.Repository(".")
|
|
18
|
+
result = PostCommitPushRemotes(repo, BRANCH_PATTERN).run()
|
|
19
|
+
return int(result.value)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if __name__ == "__main__":
|
|
23
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env -S uv run python
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__VERSION__ = "1"
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
import pygit2
|
|
11
|
+
|
|
12
|
+
from pygittools.hooks import parse_ref_updates
|
|
13
|
+
from pygittools.hooks_notify import PostReceiveNotify
|
|
14
|
+
|
|
15
|
+
NOTIFY_URL = os.environ.get("PYGITWEB_NOTIFY_URL", "http://127.0.0.1:8000/_internal/notify")
|
|
16
|
+
PROJECT = os.environ.get("PYGITWEB_PROJECT") or os.path.basename(os.getcwd()).removesuffix(".git")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main() -> int:
|
|
20
|
+
repo = pygit2.Repository(".")
|
|
21
|
+
updates = parse_ref_updates(sys.stdin)
|
|
22
|
+
result = PostReceiveNotify(repo, NOTIFY_URL, PROJECT).run(updates)
|
|
23
|
+
return int(result.value)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if __name__ == "__main__":
|
|
27
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env -S uv run python
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__VERSION__ = "1"
|
|
6
|
+
|
|
7
|
+
import pygit2
|
|
8
|
+
from pygittools.hooks_ruff import PreCommitRuff
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main() -> int:
|
|
12
|
+
repo = pygit2.Repository(".")
|
|
13
|
+
result = PreCommitRuff(repo).run()
|
|
14
|
+
return int(result.value)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env -S uv run python
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__VERSION__ = "1"
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
import pygit2
|
|
11
|
+
|
|
12
|
+
from pygittools.hooks import HookResult, parse_ref_updates
|
|
13
|
+
from pygittools.hooks_patterns import PreReceiveProtectedPattern
|
|
14
|
+
|
|
15
|
+
PROTECTED_REF_PATTERN = os.environ.get("PYGITWEB_PROTECTED_REF_PATTERN", r"^refs/heads/(main|master)$")
|
|
16
|
+
COMMIT_MSG_PATTERN = os.environ.get("PYGITWEB_COMMIT_MSG_PATTERN", r"^(\S+): (.+)")
|
|
17
|
+
REJECT_MERGE_COMMITS = os.environ.get("PYGITWEB_REJECT_MERGE_COMMITS", "").lower() in {
|
|
18
|
+
"1",
|
|
19
|
+
"true",
|
|
20
|
+
"yes",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def main() -> int:
|
|
25
|
+
repo = pygit2.Repository(".")
|
|
26
|
+
updates = parse_ref_updates(sys.stdin)
|
|
27
|
+
result = PreReceiveProtectedPattern(
|
|
28
|
+
repo,
|
|
29
|
+
PROTECTED_REF_PATTERN,
|
|
30
|
+
COMMIT_MSG_PATTERN,
|
|
31
|
+
reject_merge_commits=REJECT_MERGE_COMMITS,
|
|
32
|
+
).run(updates)
|
|
33
|
+
return int(result.value)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import IO
|
|
5
|
+
|
|
6
|
+
import pygit2
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class HookResult(Enum):
|
|
10
|
+
SUCCESS = 0
|
|
11
|
+
FAILURE = 1
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class Hook:
|
|
16
|
+
repo: pygit2.Repository
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
RefUpdate = tuple[str, str, str]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse_ref_updates(stream: IO[str] | None = None) -> list[RefUpdate]:
|
|
23
|
+
"""Parse `<old-oid> <new-oid> <ref-name>` lines (one per ref) from a receive-pack hook's stdin."""
|
|
24
|
+
source: IO[str] = stream if stream is not None else sys.stdin
|
|
25
|
+
updates: list[RefUpdate] = []
|
|
26
|
+
for raw in source:
|
|
27
|
+
parts: list[str] = raw.strip().split(maxsplit=2)
|
|
28
|
+
if len(parts) == 3:
|
|
29
|
+
updates.append((parts[0], parts[1], parts[2]))
|
|
30
|
+
return updates
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
Pre-Commit Hook (Client-side)
|
|
35
|
+
Runs before a commit is made, before a commit message is written (if not supplied by -m).
|
|
36
|
+
Use this hook to:
|
|
37
|
+
- Check for uncommitted changes
|
|
38
|
+
- Run tests, lints, security checks, etc.
|
|
39
|
+
This can be bypassed with --no-verify by the user.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class PreCommit(Hook):
|
|
44
|
+
def __init__(self, repo: pygit2.Repository):
|
|
45
|
+
super().__init__(repo)
|
|
46
|
+
|
|
47
|
+
def run(self) -> HookResult:
|
|
48
|
+
return HookResult.SUCCESS
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
Prepare-Commit-Msg Hook (Client-side)
|
|
53
|
+
Runs right after the default log message is prepared and before the editor is started.
|
|
54
|
+
Use this hook to:
|
|
55
|
+
- Edit the message file in place (e.g. strip template comments)
|
|
56
|
+
- Insert a standard prefix/suffix (e.g. branch name, ticket ID)
|
|
57
|
+
- Add Signed-off-by from a template
|
|
58
|
+
Takes 1–3 parameters: message file path, source (message|template|merge|squash|commit),
|
|
59
|
+
and optionally commit hash for amend.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class PrepareCommitMsg(Hook):
|
|
64
|
+
def __init__(self, repo: pygit2.Repository):
|
|
65
|
+
super().__init__(repo)
|
|
66
|
+
|
|
67
|
+
def run(
|
|
68
|
+
self,
|
|
69
|
+
message_file: str,
|
|
70
|
+
source: str = "",
|
|
71
|
+
commit_hash: str | None = None,
|
|
72
|
+
) -> HookResult:
|
|
73
|
+
return HookResult.SUCCESS
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
Commit-Msg Hook (Client-side)
|
|
78
|
+
Runs after the commit message is prepared; can be bypassed with --no-verify.
|
|
79
|
+
Use this hook to:
|
|
80
|
+
- Enforce a project standard format (e.g. conventional commits)
|
|
81
|
+
- Validate or normalize the message in place
|
|
82
|
+
- Reject the commit (e.g. duplicate Signed-off-by, missing ticket reference)
|
|
83
|
+
Takes one parameter: the path to the file holding the proposed commit log message.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class CommitMsg(Hook):
|
|
88
|
+
def __init__(self, repo: pygit2.Repository):
|
|
89
|
+
super().__init__(repo)
|
|
90
|
+
|
|
91
|
+
def run(self, message_file: str) -> HookResult:
|
|
92
|
+
return HookResult.SUCCESS
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
"""
|
|
96
|
+
Post-Commit Hook (Client-side)
|
|
97
|
+
Runs after a commit is made. Cannot affect the outcome of git commit.
|
|
98
|
+
Use this hook to:
|
|
99
|
+
- Notify (e.g. log, webhook, chat)
|
|
100
|
+
- Run post-commit checks or backups
|
|
101
|
+
- Update external metadata or caches
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class PostCommit(Hook):
|
|
106
|
+
def __init__(self, repo: pygit2.Repository):
|
|
107
|
+
super().__init__(repo)
|
|
108
|
+
|
|
109
|
+
def run(self) -> HookResult:
|
|
110
|
+
return HookResult.SUCCESS
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
"""
|
|
114
|
+
Pre-Merge-Commit Hook (Client-side)
|
|
115
|
+
Runs after a merge has been carried out successfully and before the merge commit
|
|
116
|
+
message is finalized; can be bypassed with --no-verify.
|
|
117
|
+
Use this hook to:
|
|
118
|
+
- Validate the merged tree (e.g. run tests on the result)
|
|
119
|
+
- Inspect or adjust the merge commit message
|
|
120
|
+
- Abort the merge commit if checks fail
|
|
121
|
+
Takes no parameters.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class PreMergeCommit(Hook):
|
|
126
|
+
def __init__(self, repo: pygit2.Repository):
|
|
127
|
+
super().__init__(repo)
|
|
128
|
+
|
|
129
|
+
def run(self) -> HookResult:
|
|
130
|
+
return HookResult.SUCCESS
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
"""
|
|
134
|
+
Pre-Rebase Hook (Client-side)
|
|
135
|
+
Called by git rebase; can be used to prevent a branch from being rebased.
|
|
136
|
+
Use this hook to:
|
|
137
|
+
- Block rebasing certain branches (e.g. main)
|
|
138
|
+
- Run checks before rewriting history
|
|
139
|
+
Takes one or two parameters: upstream ref, and optionally the branch being rebased
|
|
140
|
+
(absent when rebasing the current branch).
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class PreRebase(Hook):
|
|
145
|
+
def __init__(self, repo: pygit2.Repository):
|
|
146
|
+
super().__init__(repo)
|
|
147
|
+
|
|
148
|
+
def run(self, upstream: str, branch: str | None = None) -> HookResult:
|
|
149
|
+
return HookResult.SUCCESS
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
"""
|
|
153
|
+
Post-Checkout Hook (Client-side)
|
|
154
|
+
Runs after git checkout, git switch, or git clone (when a worktree is updated).
|
|
155
|
+
Use this hook to:
|
|
156
|
+
- Restore working tree metadata (e.g. permissions, ACLs)
|
|
157
|
+
- Auto-display differences from the previous HEAD
|
|
158
|
+
- Run repository validity checks or refresh generated files
|
|
159
|
+
Takes three parameters: previous HEAD ref, new HEAD ref, and a flag (1 = branch checkout, 0 = file checkout).
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class PostCheckout(Hook):
|
|
164
|
+
def __init__(self, repo: pygit2.Repository):
|
|
165
|
+
super().__init__(repo)
|
|
166
|
+
|
|
167
|
+
def run(self, prev_head: str, new_head: str, branch_checkout: str) -> HookResult:
|
|
168
|
+
return HookResult.SUCCESS
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
"""
|
|
172
|
+
Post-Merge Hook (Client-side)
|
|
173
|
+
Runs after a successful git merge (e.g. after git pull). Cannot affect the outcome.
|
|
174
|
+
Use this hook to:
|
|
175
|
+
- Restore working tree metadata in conjunction with pre-commit
|
|
176
|
+
- Run post-merge checks or notifications
|
|
177
|
+
Takes one parameter: a status flag indicating whether the merge was a squash merge.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class PostMerge(Hook):
|
|
182
|
+
def __init__(self, repo: pygit2.Repository):
|
|
183
|
+
super().__init__(repo)
|
|
184
|
+
|
|
185
|
+
def run(self, squash: str) -> HookResult:
|
|
186
|
+
return HookResult.SUCCESS
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
"""
|
|
190
|
+
Pre-Push Hook (Client-side)
|
|
191
|
+
Called by git push; can be used to prevent a push.
|
|
192
|
+
Use this hook to:
|
|
193
|
+
- Run tests or lint before pushing
|
|
194
|
+
- Enforce branch naming or ref permissions
|
|
195
|
+
- Validate commits being pushed
|
|
196
|
+
Takes two parameters: remote name and remote URL. Ref updates are provided on stdin.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class PrePush(Hook):
|
|
201
|
+
def __init__(self, repo: pygit2.Repository):
|
|
202
|
+
super().__init__(repo)
|
|
203
|
+
|
|
204
|
+
def run(self, remote_name: str, remote_url: str) -> HookResult:
|
|
205
|
+
return HookResult.SUCCESS
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# -----------------------------------------------------------------------------
|
|
209
|
+
# Server-side hooks (run in $GIT_DIR on receive-pack / push)
|
|
210
|
+
# -----------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
"""
|
|
213
|
+
Update Hook (Server-side)
|
|
214
|
+
Invoked by git-receive-pack once per ref being updated, before the ref is updated.
|
|
215
|
+
Use this hook to:
|
|
216
|
+
- Enforce fast-forward only (reject non-FF updates)
|
|
217
|
+
- Implement per-ref access control
|
|
218
|
+
- Log or validate old → new for specific refs
|
|
219
|
+
Takes three parameters: ref name, old object name, new object name.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class Update(Hook):
|
|
224
|
+
def __init__(self, repo: pygit2.Repository):
|
|
225
|
+
super().__init__(repo)
|
|
226
|
+
|
|
227
|
+
def run(self, ref_name: str, old_oid: str, new_oid: str) -> HookResult:
|
|
228
|
+
return HookResult.SUCCESS
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
"""
|
|
232
|
+
Post-Update Hook (Server-side)
|
|
233
|
+
Invoked by git-receive-pack once after all refs have been updated.
|
|
234
|
+
Use this hook to:
|
|
235
|
+
- Notify or trigger CI for updated refs
|
|
236
|
+
- Run git update-server-info for dumb transports (e.g. HTTP)
|
|
237
|
+
- Update caches or derived data
|
|
238
|
+
Takes a variable number of parameters: the name of each ref that was updated.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class PostUpdate(Hook):
|
|
243
|
+
def __init__(self, repo: pygit2.Repository):
|
|
244
|
+
super().__init__(repo)
|
|
245
|
+
|
|
246
|
+
def run(self, *ref_names: str) -> HookResult:
|
|
247
|
+
return HookResult.SUCCESS
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
"""
|
|
251
|
+
Push-To-Checkout Hook (Server-side)
|
|
252
|
+
Invoked when a push updates the currently checked-out branch and receive.denyCurrentBranch is updateInstead.
|
|
253
|
+
Use this hook to:
|
|
254
|
+
- Override how the working tree and index are updated to match the new commit
|
|
255
|
+
- Run git read-tree -u -m to emulate a reverse fetch
|
|
256
|
+
- Refuse the push by exiting non-zero (without modifying index or worktree)
|
|
257
|
+
Takes one parameter: the commit object name the tip of the current branch will be updated to.
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class PushToCheckout(Hook):
|
|
262
|
+
def __init__(self, repo: pygit2.Repository):
|
|
263
|
+
super().__init__(repo)
|
|
264
|
+
|
|
265
|
+
def run(self, new_commit: str) -> HookResult:
|
|
266
|
+
return HookResult.SUCCESS
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
"""
|
|
270
|
+
Pre-Auto-GC Hook
|
|
271
|
+
Invoked by git gc --auto before automatic garbage collection runs.
|
|
272
|
+
Use this hook to:
|
|
273
|
+
- Prevent or delay gc when the repo is busy (e.g. long-running operations)
|
|
274
|
+
- Run housekeeping or consistency checks before gc
|
|
275
|
+
- Notify or log that auto-gc is about to run
|
|
276
|
+
Takes no parameters. Exiting with non-zero status prevents gc from running.
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class PreAutoGc(Hook):
|
|
281
|
+
def __init__(self, repo: pygit2.Repository):
|
|
282
|
+
super().__init__(repo)
|
|
283
|
+
|
|
284
|
+
def run(self) -> HookResult:
|
|
285
|
+
return HookResult.SUCCESS
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
"""
|
|
289
|
+
Pre-Receive Hook (Server-side)
|
|
290
|
+
Invoked by git-receive-pack once before any refs are updated. Receives ref updates on
|
|
291
|
+
stdin as `<old-oid> <new-oid> <ref-name>` lines (one per ref). Exiting non-zero rejects
|
|
292
|
+
the entire push (no refs are updated).
|
|
293
|
+
Use this hook to:
|
|
294
|
+
- Reject pushes that violate global policy (e.g. force-push to protected refs)
|
|
295
|
+
- Notify a long-poll change queue so subscribers can react quickly
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class PreReceive(Hook):
|
|
300
|
+
def __init__(self, repo: pygit2.Repository):
|
|
301
|
+
super().__init__(repo)
|
|
302
|
+
|
|
303
|
+
def run(self, ref_updates: list[RefUpdate]) -> HookResult:
|
|
304
|
+
return HookResult.SUCCESS
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
"""
|
|
308
|
+
Post-Receive Hook (Server-side)
|
|
309
|
+
Invoked by git-receive-pack once after all refs have been updated. Receives ref updates
|
|
310
|
+
on stdin in the same format as pre-receive. Exit code does not affect the receive.
|
|
311
|
+
Use this hook to:
|
|
312
|
+
- Notify caches / long-poll subscribers (refs are visible at this point)
|
|
313
|
+
- Trigger CI, mirroring, or webhook delivery
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class PostReceive(Hook):
|
|
318
|
+
def __init__(self, repo: pygit2.Repository):
|
|
319
|
+
super().__init__(repo)
|
|
320
|
+
|
|
321
|
+
def run(self, ref_updates: list[RefUpdate]) -> HookResult:
|
|
322
|
+
return HookResult.SUCCESS
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
# -----------------------------------------------------------------------------
|
|
326
|
+
# Hooks skipped (not implemented in this module)
|
|
327
|
+
# -----------------------------------------------------------------------------
|
|
328
|
+
#
|
|
329
|
+
# E-mail / git-am hooks (skipped by design):
|
|
330
|
+
# - applypatch-msg (message file; used by git am)
|
|
331
|
+
# - pre-applypatch (no params; used by git am)
|
|
332
|
+
# - post-applypatch (no params; used by git am)
|
|
333
|
+
#
|
|
334
|
+
# Other stdin-only / protocol hooks (skipped: pkt-line or transactional state):
|
|
335
|
+
# - reference-transaction (state string + ref updates on stdin)
|
|
336
|
+
# - proc-receive (pkt-line protocol on stdin/stdout)
|