git 如何从 commit-msg 挂钩中提示用户?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3417896/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-19 04:28:08  来源:igfitidea点击:

How do I prompt the user from within a commit-msg hook?

githookcommit-message

提问by Ben Hocking

I want to warn the user if their commit message doesn't follow a certain set of guidelines, and then give them the option to edit their commit message, ignore the warning, or cancel the commit. The problem is that I don't seem to have access to stdin.

我想警告用户如果他们的提交消息没有遵循一组特定的准则,然后让他们选择编辑他们的提交消息、忽略警告或取消提交。问题是我似乎无法访问标准输入。

Below is my commit-msg file:

下面是我的 commit-msg 文件:

function verify_info {
    if [ -z "$(grep ':.*[a-zA-Z]' )" ]
    then
        echo >&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} 
                    verify_info "" 
                    ;;
                *)  echo "CHOICE = ${CHOICE}"
                    exit 1
                    ;;
            esac
        done
    fi
}

verify_info "" "Scope"
if [ $# -ne 0 ];
then
    exit $#
fi
verify_info "" "Affects"
if [ $# -ne 0 ];
then
    exit $#
fi

exit 0

Here is the output when I leave the Scope information blank:

这是我将 Scope 信息留空时的输出:

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 doesn't actually stop for input. I've also tried using the simpler "read" command, and it has the same problem. It seems that the problem is that at this point git has control of stdin and is providing its own input. How do I fix this?

该消息是正确的,但实际上并没有停止输入。我也试过使用更简单的“读取”命令,它也有同样的问题。似乎问题在于此时 git 可以控制 stdin 并提供自己的输入。我该如何解决?

Update: It seems this might be a duplicate of this questionwhich unfortunately seems to suggest I'm out of luck.

更新:似乎这可能是这个问题的重复,不幸的是,这似乎表明我不走运。

回答by Eliot Sykes

Calling exec < /dev/ttyassigns standard input to the keyboard. Works for me in a post-commit git hook:

Callingexec < /dev/tty将标准输入分配给键盘。在提交后的 git 钩子中对我有用:

#!/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

回答by Frosty

The commit-msghook is not run in an interactive environment (as you have noticed).

commit-msg钩子不在交互式环境中运行(如您所见)。

The only way to reliable notify the user would be to write an error to stdout, place a copy of the commit message in a BAD_MSGfile and instruct the user to edit the file and git commit --file=BAD_MSG

可靠地通知用户的唯一方法是将错误写入标准输出,将提交消息的副本放入BAD_MSG文件并指示用户编辑该文件并git commit --file=BAD_MSG



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

如果您对环境有一定的控制权,您可以拥有一个备用编辑器,它是一个包装脚本,用于检查建议的消息,并可以使用额外的注释消息重新启动编辑器。

Basically, you run the editor, check the file saved against your rules. and if it fails, prepend your warning message (with leading #) to the file and restart the editor.

基本上,您运行编辑器,根据您的规则检查保存的文件。如果失败,请将您的警告消息(带前导#)添加到文件中并重新启动编辑器。

You could even allow them to put in a #FORCE=trueline in the message which would suppress the check and continue.

您甚至可以允许他们#FORCE=true在消息中加入一行,以抑制检查并继续。

回答by dosentmatter

How to do it in Node.js or TypeScript

如何在 Node.js 或 TypeScript 中实现

EDIT:I made an npm package

编辑:我做了一个npm 包



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

我看到人们在Eliot Sykes answer 中评论如何为其他语言做这件事,但 JavaScript 解决方案有点长,所以我会做一个单独的答案。

I'm not sure if O_NOCTTYis required, but it doesn't seem to affect anything. I don't really understand what a controlling terminal is. GNU docs description. I think what it means is that with O_NOCTTYon, you wouldn't be able to send a CTRL+Cto the process (if it doesn't already have a controlling terminal). In that case, I'll leave it on so you don't control spawned processes. The main node process should already have a controlling terminal, I think.

我不确定是否O_NOCTTY需要,但它似乎没有任何影响。我真的不明白控制终端是什么。GNU 文档说明。我认为这意味着O_NOCTTY打开后,您将无法CTRL+C向进程发送 a (如果它还没有控制终端)。在这种情况下,我会保留它,这样您就不会控制生成的进程。我认为主节点进程应该已经有一个控制终端。

I adapted the answer from this GitHub issue

我改编了这个GitHub 问题的答案

I don't see any docs on how to use the tty.ReadStreamconstructor so I did a bit of trial and error / digging through Node.js source code.

我没有看到任何关于如何使用tty.ReadStream构造函数的文档,所以我做了一些反复试验/挖掘Node.js 源代码

You have to use Object.definePropertybecause Node.js internalsuses it too, and doesn't define a setter. An alternative is to do process.stdin.fd = fd, but I get duplicate output that way.

您必须使用它,Object.defineProperty因为Node.js 内部也使用它,并且没有定义 setter。另一种方法是 do process.stdin.fd = fd,但我以这种方式得到重复的输出。

Anyway, I wanted to use this with Husky.jsand it seems to work so far. I should probably turn this into an npm package when I get the time.

无论如何,我想将它与Husky.js一起使用,到目前为止它似乎工作正常。当我有时间时,我可能应该把它变成一个 npm 包。

Node.js

节点.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);

回答by perx

To make selectstop for input, you may also try to redirect the stdinof selectfrom /dev/fd/3(See: Read input in bash inside a while loop).

为了select停止输入,你也可以尝试重定向stdinselect/dev/fd/3(见:在bash读取输入一个while循环中)。

# 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} 
             echo verify_info "" 
             ;;
         *)  echo "CHOICE = ${CHOICE}"
             exit 1
             ;;
      esac
   done 0<&3 3<&-
done
} 3<&- 3<&0

回答by Louis BAYLE

this works fine when running git commit from command line. On windows (haven't tryed on linux) if you use gitk or git-gui you won't be able to prompt because you get an error on the "exec < /dev/tty" line.

这在从命令行运行 git commit 时工作正常。在 Windows 上(尚未在 linux 上尝试过),如果您使用 gitk 或 git-gui,您将无法提示,因为您在“exec < /dev/tty”行上收到错误。

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

解决方法是在你的钩子中调用 git-bash.exe:

.git/hooks/post-commit contains:

.git/hooks/post-commit 包含:

#!/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:

.git/hooks/post-commit.sh 文件包含:

# --------------------------------------------------------
# usage: f_askContinue "my question ?"
function f_askContinue {
  local myQuestion=

  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 !"

回答by NickUnuchek

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