How can I request a user from the commit-msg hook?

I want to warn the user that the message about their commit does not correspond to a specific set of manuals, and then gives them the opportunity to edit the message about their commit, ignore the warning, or cancel the commit. The problem is that I do not have access to stdin.

Below is my commit-msg file:

function verify_info { if [ -z "$(grep '$2:.*[a-zA-Z]' $1)" ] then echo >&2 $2 information should not be omitted local_editor=`git config --get core.editor` if [ -z "${local_editor}" ] then local_editor=${EDITOR} fi echo "Do you want to" select CHOICE in "edit the commit message" "ignore this warning" "cancel the commit"; do case ${CHOICE} in i*) echo "Warning ignored" ;; e*) ${local_editor} $1 verify_info "$1" $2 ;; *) echo "CHOICE = ${CHOICE}" exit 1 ;; esac done fi } verify_info "$1" "Scope" if [ $# -ne 0 ]; then exit $# fi verify_info "$1" "Affects" if [ $# -ne 0 ]; then exit $# fi exit 0 

Here is the result when I leave the area information visible:

 Scope information should not be omitted Do you want to: 1) edit the commit message 3) cancel the commit 2) ignore this warning #? 

The message is correct, but it does not actually stop to enter. I also tried using a simpler read command and it has the same problem. It seems that the problem is that git currently has control over stdin and provides its own input. How to fix it?

Update: it looks like this might be a duplicate of this question , which unfortunately seems like I was out of luck.

+68
git commit-message hook
Aug 05 '10 at 18:27
source share
6 answers

A call to exec < /dev/tty assigns standard keyboard input. Works for me in post-commit git hook:

 #!/bin/sh echo "[post-commit hook] Commit done!" # Allows us to read user input below, assigns stdin to keyboard exec < /dev/tty while true; do read -p "[post-commit hook] Check for outdated gems? (Y/n) " yn if [ "$yn" = "" ]; then yn='Y' fi case $yn in [Yy] ) bundle outdated --pre; break;; [Nn] ) exit;; * ) echo "Please answer y or n for yes or no.";; esac done 
+144
Apr 04 2018-12-12T00:
source share

The commit-msg hook does not start in an interactive environment (as you noticed).

The only way to reliably notify the user is to write the error to standard output, put a copy of the commit message in the BAD_MSG file and instruct the user to edit the file and git commit --file=BAD_MSG




If you have some control over the environment, you can have an alternative editor, which is a script wrapper that checks the proposed message and can restart the editor with an additional commented message.

In fact, you start the editor, check the saved file for compliance with your rules. and in case of failure, add a warning (with # ) to the file and restart the editor.

You could even let them insert the line #FORCE=true in the message, which will suppress the check and continue working.

+4
Aug 20 '10 at 18:23
source share

To make select stop for input, you can also redirect stdin from select from /dev/fd/3 (see Read input in bash inside while loop).

 # sample code using a while loop to simulate git consuming stdin { echo 'fd 0' | while read -r stdin; do echo "stdin: $stdin" echo "Do you want to" select CHOICE in "edit the commit message" "ignore this warning" "cancel the commit"; do case ${CHOICE} in i*) echo "Warning ignored" ;; e*) echo ${local_editor} $1 echo verify_info "$1" $2 ;; *) echo "CHOICE = ${CHOICE}" exit 1 ;; esac done 0<&3 3<&- done } 3<&- 3<&0 
+1
May 03 '13 at 11:27
source share

this works fine when running git commit from the command line. On windows (not tried on linux), if you use gitk or git -gui, you will not be able to request because you get an error message in the line "exec </ dev / tty".

The solution is to call git - bash.exe in your hook:

.git / hooks / post-commit contains:

 #!/bin/sh exec /c/Program\ Files/Git/git-bash.exe /path/to/my_repo/.git/hooks/post-checkout.sh 

The .git / hooks / post-commit.sh file contains:

 # -------------------------------------------------------- # usage: f_askContinue "my question ?" function f_askContinue { local myQuestion=$1 while true; do read -p "${myQuestion} " -n 1 -r answer case $answer in [Yy]* ) printf "\nOK\n"; break;; [Nn]* ) printf "\nAbandon\n"; exit;; * ) printf "\nAnswer with Yes or No.\n";; esac done } f_askContinue "Do you want to continue ?" echo "This command is executed after the prompt !" 
+1
Apr 26 '17 at 11:58
source share

How to do it in Node.js or TypeScript

UPDATE: I made a package in a minute




I see people commenting on how to do this for other languages ​​in Eliot Sykes' answer, but the JavaScript solution is a bit long, so I will make a separate answer.

I'm not sure if O_NOCTTY is O_NOCTTY , but it does not seem to affect anything. I do not really understand what a control terminal is. Description of GNU documents . I think this means that with O_NOCTTY enabled O_NOCTTY you cannot send CTRL+C process (if it does not already have a control terminal). In this case, I will leave it turned on so that you do not control the spawned processes. I think that the process of the main node should already have a control terminal.

I adapted the answer from this GitHub question

I do not see any documents on how to use the tty.ReadStream constructor, so I made some trial and error / delved into the source code of Node.js.

You should use Object.defineProperty because the internal device of Node.js also uses it and does not define a setter. An alternative is to do process.stdin.fd = fd , but I get duplicate output in this way.

Anyway, I wanted to use this with Husky.js , and it seems to be working so far. I should probably turn this into an npm package when I have time.

Node.js

 #!/usr/bin/env node const fs = require('fs'); const tty = require('tty'); if (!process.stdin.isTTY) { const { O_RDONLY, O_NOCTTY } = fs.constants; let fd; try { fd = fs.openSync('/dev/tty', O_RDONLY + O_NOCTTY); } catch (error) { console.error('Please push your code in a terminal.'); process.exit(1); } const stdin = new tty.ReadStream(fd); Object.defineProperty(process, 'stdin', { configurable: true, enumerable: true, get: () => stdin, }); } ...Do your stuff... process.stdin.destroy(); process.exit(0); 

Typescript:

 #!/usr/bin/env ts-node import fs from 'fs'; import tty from 'tty'; if (!process.stdin.isTTY) { const { O_RDONLY, O_NOCTTY } = fs.constants; let fd; try { fd = fs.openSync('/dev/tty', O_RDONLY + O_NOCTTY); } catch (error) { console.error('Please push your code in a terminal.'); process.exit(1); } // @ts-ignore: 'ReadStream' in @types/node incorrectly expects an object. // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/37174 const stdin = new tty.ReadStream(fd); Object.defineProperty(process, 'stdin', { configurable: true, enumerable: true, get: () => stdin, }); } ...Do your stuff... process.stdin.destroy(); process.exit(0); 
+1
Jul 27 '19 at 2:18
source share
 read -p "Question? [y|n] " -n 1 -r < /dev/tty echo if echo $REPLY | grep -E '^[Yy]$' > /dev/null; then #do if Yes else #do if No fi 
0
Feb 25 '19 at 13:05
source share



All Articles