【问题标题】:How do I prompt the user from within a commit-msg hook?如何从 commit-msg 挂钩中提示用户?
【发布时间】:2011-03-25 23:27:57
【问题描述】:

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

下面是我的 commit-msg 文件:

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

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

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

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

更新:看来这可能是 this question 的副本,不幸的是这似乎表明我不走运。

【问题讨论】:

  • 当您可以访问 X 服务器时,您可以转至图形对话工具。丑陋但有效
  • 您可以简单地提供信息性错误消息来代替错误消息 - 包括回显必要的命令以忽略警告。
  • @btspierre,这就是我最终采用的方法。在 John Feminella 的建议下,我允许使用环境变量来覆盖警告,并在遇到不好的情况时回显警告。
  • @Rudi:我不确定你会逃到 X 服务器,因为 git 似乎完全控制了标准输入。
  • 10 年后,正在讨论这个问题:public-inbox.org/git/…

标签: git hook commit-message


【解决方案1】:

调用exec < /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

【讨论】:

  • STDIN 可以用exec &lt;&amp;-再次关闭
  • 如果您使用的是 Ruby,那将转换为 STDIN.reopen('/dev/tty')。很棒的东西,这才是真正的答案。
  • 这很棒,但是从其他工具(例如编辑器)提交时可能会中断。不知道如何解决这个问题,但如果有人有想法,我很想听听。
  • 这甚至适用于bash 下的MingW32(GNU-ish Windows 层)。谢谢!
  • 如果您使用的是 Python,那将转换为 sys.stdin = open("/dev/tty", "r")。可能会帮助某人:)
【解决方案2】:

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

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


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

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

您甚至可以允许他们在消息中添加#FORCE=true 行,这将禁止检查并继续。

【讨论】:

  • 尝试在脚本中首先调用 exec &lt; /dev/tty 以便能够捕获用户输入 - 请参阅其他答案。
  • 虽然这是一个深思熟虑的答案,但应该接受艾略特的答案,因为他证明了它实际上是可能的!
  • 用户可以跳过--no-verify的钩子,需要使用#FORCE=truehackery。
【解决方案3】:

这在从命令行运行 git commit 时可以正常工作。在 windows 上(在 linux 上没有尝试过)如果你使用 gitk 或 git-gui 你将无法提示,因为你在 "exec

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

.git/hooks/post-commit 包含:

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

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

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

【讨论】:

    【解决方案4】:
    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
    

    【讨论】:

    • 谢谢。我喜欢这个答案。对于 zsh 只需执行 read "?Question? [y|n]" &lt; /dev/tty 无需额外选项
    • 这也适用于允许运行docker exec -it(这会改变 php-doc-check 的输出格式)
    【解决方案5】:

    如何在 Node.js 或 TypeScript 中实现

    编辑:我做了一个npm package


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

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

    我改编了这个GitHub issue的答案

    我没有看到任何关于如何使用 tty.ReadStream 构造函数的文档,所以我做了一些试验和错误/挖掘 Node.js source code

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

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

    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);
    

    打字稿:

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

    【讨论】:

    • 嗨,我是 node 和 git 中的菜鸟,我打算在 node js 中编写钩子,我有一个场景,我需要捕获用户输入以运行 eslint,无论我做什么钩子执行发生时不会暂停用户输入,然后我读到我们必须附加 dev/tty 我在谷歌上搜索了很多关于如何做到这一点,遇到了你的 repo,我想也许你会有一些相同的想法,如果这个不是正确的联系地点,我提前道歉,你能告诉我如何在用节点编写的 git hooks 中捕获用户输入吗?你的包裹能帮到我吗?
    • 我认为你是在我的repo 上发帖的同一个人。我认为您在 Windows 上,而 /dev/tty 在 Windows 上不存在。您可以尝试使用CON 设备。
    【解决方案6】:

    要使select 停止输入,您还可以尝试从/dev/fd/3 重定向selectstdin(参见:Read input in bash inside a 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-11
      • 1970-01-01
      • 1970-01-01
      • 2020-12-11
      • 2015-08-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-16
      相关资源
      最近更新 更多