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


72

如果用户的提交消息未遵循某些准则,我想警告用户,然后为他们提供编辑其提交消息,忽略警告或取消提交的选项。问题是我似乎无法访问stdin。

以下是我的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 information should not be omitted
Do you want to:
1) edit the commit message  3) cancel the commit
2) ignore this warning
#?

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

更新:看来这可能是这个问题的重复,很遗憾,似乎表明我不走运。


当您有权访问X Server时,可以转至图形对话框工具。丑陋但
可行

除了提供错误消息外,您还可以提供一条提示性错误消息-包括回显必要的命令以忽略警告。
bstpierre 2010年

@btspierre,这就是我最终采用的方法。在约翰·费米内拉(John Feminella)的建议下,我允许使用环境变量覆盖警告,并且在遇到不良情况时仅回显警告。
本·霍金

@Rudi:我不确定您会逃到X Server的什么,因为git似乎可以完全控制stdin。
本·霍金

1
10年后,正在对此进行讨论:public-inbox.org/git/…–
VonC

Answers:


155

调用exec < /dev/tty将标准输入分配给键盘。在提交后的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

22
STDIN可以通过exec <&-
Andy

16
如果您使用的是Ruby,它将转换为STDIN.reopen('/dev/tty')。很棒的东西,这才是真正的答案。
Matthew Scharley 2013年

6
很棒,但是当从其他工具(例如编辑器)提交时,它可能会中断。不知道如何解决这个问题,但是如果有人有想法,我很想听听。
Peeja 2013年

3
这甚至在工作bash的mingw32(GNU十岁上下的Windows层)。谢谢!
Dan Lenski 2014年

11
如果您使用的是Python,它将转换为sys.stdin = open("/dev/tty", "r")。可能会帮助某人:)
filaton '16

6

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

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


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

基本上,您运行编辑器,根据规则检查保存的文件。如果失败,请在警告信息前加上(#在文件开头),然后重新启动编辑器。

您甚至可以允许他们#FORCE=true在消息中插入一行,这将取消检查并继续。


7
尝试exec < /dev/tty先在脚本中调用以捕获用户输入-请参见其他答案。
艾略特·赛克斯

虽然这是一个深思熟虑的答案,因为他证明了这一点艾略特应该被接受实际上是可能的!
马特·弗莱彻

1
用户可以跳过钩子--no-verify,确实需要借助#FORCE=true黑客手段。
fsaintjacques's

2

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

解决方法是在您的钩子中调用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 !"

2

如何在Node.js或TypeScript中做到这一点

编辑:我做了一个npm包


我看到人们在Eliot Sykes答案中评论如何针对其他语言进行操作,但是JavaScript解决方案有点长,所以我将给出一个单独的答案。

我不确定是否O_NOCTTY需要,但似乎没有任何影响。我不太了解什么是控制终端。GNU文档描述。我认为这意味着O_NOCTTY打开后,您将无法CTRL+C向过程发送a (如果它还没有控制终端)。在这种情况下,我将其保持打开状态,以便您不控制生成的进程。我认为,主节点进程应该已经具有控制终端。

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

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

您必须使用,Object.defineProperty因为Node.js内部构件也使用它,并且没有定义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中编写钩子,我有一个scenrio,我需要捕获用户输入以运行eslint,无论我做什么,钩子执行都会在不暂停用户输入的情况下发生,然后我读到,我们必须附加dev / tty,我经常在如何使用该工具上进行大量搜索,遇到了您的存储库,我想如果您不是在此联系的正确地点,也许您会对此有所了解,提前致歉,能否让我知道如何在node编写的git hook中捕获用户输入?您的包裹会帮助我吗?
user1651070

我认为您和我的回购邮件中发布的是同一个人。我认为您在Windows上并且在Windows/dev/tty上不存在。您可以尝试使用CON设备。
剂量ntmatter

1

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

0
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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.