Skip to content

feat(commands/commit): apply prepare-commit-msg hook #250

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,12 @@
language: python
language_version: python3
minimum_pre_commit_version: "1.4.3"

- id: commitizen-prepare-commit-msg
name: commitizen prepare commit msg
description: "prepare commit message"
entry: cz commit --commit-msg-file
language: python
language_version: python3
require_serial: true
minimum_pre_commit_version: "1.4.3"
8 changes: 8 additions & 0 deletions commitizen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,14 @@ def __call__(
"dest": "double_dash",
"help": "Positional arguments separator (recommended)",
},
{
"name": "--commit-msg-file",
"help": (
"ask for the name of the temporal file that contains "
"the commit message. "
"Using it in a git hook script: MSG_FILE=$1"
),
},
],
},
{
Expand Down
34 changes: 32 additions & 2 deletions commitizen/commands/commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import shutil
import subprocess
import tempfile
from os.path import exists

import questionary

Expand All @@ -24,6 +25,7 @@
NothingToCommitError,
)
from commitizen.git import smart_open
from commitizen.wrap_stdio import unwrap_stdio, wrap_stdio


class Commit:
Expand Down Expand Up @@ -92,6 +94,17 @@ def manual_edit(self, message: str) -> str:
file.unlink()
return message

def is_blank_commit_file(self, filename) -> bool:
if not exists(filename):
return True
with open(filename) as f:
for x in f.readlines():
if len(x) == 0 or x[0] == "#":
continue
elif x[0] != "\r" and x[0] != "\n":
return False
return True

def __call__(self):
extra_args: str = self.arguments.get("extra_cli_args", "")

Expand All @@ -105,6 +118,12 @@ def __call__(self):
if is_all:
c = git.add("-u")

commit_msg_file: str = self.arguments.get("commit_msg_file")
if commit_msg_file:
if not self.is_blank_commit_file(commit_msg_file):
return
wrap_stdio()

if git.is_staging_clean() and not (dry_run or allow_empty):
raise NothingToCommitError("No files added to staging!")

Expand All @@ -126,9 +145,11 @@ def __call__(self):
else:
m = self.prompt_commit_questions()

if commit_msg_file:
unwrap_stdio()

if manual_edit:
m = self.manual_edit(m)

out.info(f"\n{m}\n")

if write_message_to_file:
Expand All @@ -138,9 +159,18 @@ def __call__(self):
if dry_run:
raise DryRunExit()

if commit_msg_file:
default_message = ""
with open(commit_msg_file) as f:
default_message = f.read()
with open(commit_msg_file, "w") as f:
f.write(m)
f.write(default_message)
out.success("Commit message is successful!")
return

always_signoff: bool = self.config.settings["always_signoff"]
signoff: bool = self.arguments.get("signoff")

if signoff:
out.warn(
"signoff mechanic is deprecated, please use `cz commit -- -s` instead."
Expand Down
16 changes: 16 additions & 0 deletions commitizen/wrap_stdio/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import sys

if sys.platform == "win32": # pragma: no cover
from .windows import _unwrap_stdio, _wrap_stdio
elif sys.platform == "linux":
from .linux import _unwrap_stdio, _wrap_stdio # pragma: no cover
else:
from .unix import _unwrap_stdio, _wrap_stdio # pragma: no cover


def wrap_stdio():
_wrap_stdio()


def unwrap_stdio():
_unwrap_stdio()
59 changes: 59 additions & 0 deletions commitizen/wrap_stdio/linux.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import sys

if sys.platform == "linux": # pragma: no cover
import os

class WrapStdinLinux:
def __init__(self):
fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
tty = open(fd, "wb+", buffering=0)
self.tty = tty

def __getattr__(self, key):
if key == "encoding":
return "UTF-8"
return getattr(self.tty, key)

def __del__(self):
self.tty.close()

class WrapStdoutLinux:
def __init__(self):
tty = open("/dev/tty", "w")
self.tty = tty

def __getattr__(self, key):
return getattr(self.tty, key)

def __del__(self):
self.tty.close()

backup_stdin = None
backup_stdout = None
backup_stderr = None

def _wrap_stdio():
global backup_stdin
backup_stdin = sys.stdin
sys.stdin = WrapStdinLinux()

global backup_stdout
backup_stdout = sys.stdout
sys.stdout = WrapStdoutLinux()

global backup_stderr
backup_stderr = sys.stderr
sys.stderr = WrapStdoutLinux()

def _unwrap_stdio():
global backup_stdin
sys.stdin.close()
sys.stdin = backup_stdin

global backup_stdout
sys.stdout.close()
sys.stdout = backup_stdout

global backup_stderr
sys.stderr.close()
sys.stderr = backup_stderr
68 changes: 68 additions & 0 deletions commitizen/wrap_stdio/unix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import sys

if sys.platform != "win32" and sys.platform != "linux": # pragma: no cover
import os
import selectors
from asyncio import (
DefaultEventLoopPolicy,
SelectorEventLoop,
get_event_loop_policy,
set_event_loop_policy,
)
from io import IOBase

class WrapStdioUnix:
def __init__(self, stdx: IOBase):
self._fileno = stdx.fileno()
fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
if self._fileno == 0:
tty = open(fd, "wb+", buffering=0)
else:
tty = open(fd, "rb+", buffering=0)
self.tty = tty

def __getattr__(self, key):
if key == "encoding":
return "UTF-8"
return getattr(self.tty, key)

backup_event_loop_policy = None
backup_stdin = None
backup_stdout = None
backup_stderr = None

def _wrap_stdio():
global backup_event_loop_policy
backup_event_loop_policy = get_event_loop_policy()

event_loop = DefaultEventLoopPolicy()
event_loop.set_event_loop(SelectorEventLoop(selectors.SelectSelector()))
set_event_loop_policy(event_loop)

global backup_stdin
backup_stdin = sys.stdin
sys.stdin = WrapStdioUnix(sys.stdin)

global backup_stdout
backup_stdout = sys.stdout
sys.stdout = WrapStdioUnix(sys.stdout)

global backup_stderr
backup_stdout = sys.stderr
sys.stderr = WrapStdioUnix(sys.stderr)

def _unwrap_stdio():
global backup_event_loop_policy
set_event_loop_policy(backup_event_loop_policy)

global backup_stdin
sys.stdin.close()
sys.stdin = backup_stdin

global backup_stdout
sys.stdout.close()
sys.stdout = backup_stdout

global backup_stderr
sys.stderr.close()
sys.stderr = backup_stderr
58 changes: 58 additions & 0 deletions commitizen/wrap_stdio/windows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import sys

if sys.platform == "win32": # pragma: no cover
import msvcrt
import os
from ctypes import c_ulong, windll # noqa
from ctypes.wintypes import HANDLE
from io import IOBase

STD_INPUT_HANDLE = c_ulong(-10)
STD_OUTPUT_HANDLE = c_ulong(-11)

class WrapStdioWindows:
def __init__(self, stdx: IOBase):
self._fileno = stdx.fileno()
if self._fileno == 0:
fd = os.open("CONIN$", os.O_RDWR | os.O_BINARY)
tty = open(fd)
handle = HANDLE(msvcrt.get_osfhandle(fd)) # noqa
windll.kernel32.SetStdHandle(STD_INPUT_HANDLE, handle)
elif self._fileno == 1:
fd = os.open("CONOUT$", os.O_RDWR | os.O_BINARY)
tty = open(fd, "w")
handle = HANDLE(msvcrt.get_osfhandle(fd)) # noqa
windll.kernel32.SetStdHandle(STD_OUTPUT_HANDLE, handle)
else:
raise Exception("not defined type")
self._tty = tty

def __getattr__(self, key):
if key == "encoding" and self._fileno == 0:
return "UTF-8"
return getattr(self._tty, key)

def __del__(self):
if "_tty" in self.__dict__:
self._tty.close()

backup_stdin = None
backup_stdout = None

def _wrap_stdio():
global backup_stdin
backup_stdin = sys.stdin
sys.stdin = WrapStdioWindows(sys.stdin)

global backup_stdout
backup_stdout = sys.stdout
sys.stdout = WrapStdioWindows(sys.stdout)

def _unwrap_stdio():
global backup_stdin
sys.stdin.close()
sys.stdin = backup_stdin

global backup_stdout
sys.stdout.close()
sys.stdout = backup_stdout
5 changes: 4 additions & 1 deletion docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,14 @@ repos:
- id: commitizen
- id: commitizen-branch
stages: [push]
- id: commitizen-prepare-commit-msg
stages: [prepare-commit-msg]
```

After the configuration is added, you'll need to run:

```sh
pre-commit install --hook-type commit-msg --hook-type pre-push
pre-commit install --hook-type commit-msg --hook-type pre-push --hook-type prepare-commit-msg
```

If you aren't using both hooks, you needn't install both stages.
Expand All @@ -109,6 +111,7 @@ If you aren't using both hooks, you needn't install both stages.
| ----------------- | ----------------- |
| commitizen | commit-msg |
| commitizen-branch | pre-push |
| commitizen-prepare-commit-msg | prepare-commit-msg |

Note that pre-commit discourages using `master` as a revision, and the above command will print a warning. You should replace the `master` revision with the [latest tag](https://github.com/commitizen-tools/commitizen/tags). This can be done automatically with:

Expand Down
Loading
Loading