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

发布于 2024-09-13 05:55:32 字数 1501 浏览 12 评论 0原文

我想警告用户,如果他们的提交消息不遵循一组特定的准则,然后为他们提供编辑提交消息、忽略警告或取消提交的选项。问题是我似乎无法访问 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
#?

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

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

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:

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 output when I leave the Scope information blank:

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?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

咽泪装欢 2024-09-20 05:55:32

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

Calling exec < /dev/tty assigns standard input to the keyboard. Works for me in a 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
古镇旧梦 2024-09-20 05:55:32

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

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


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

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

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

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

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_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 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=true line in the message which would suppress the check and continue.

南街九尾狐 2024-09-20 05:55:32
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
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
第几種人 2024-09-20 05:55:32

从命令行运行 git commit 时它工作正常,但在 Windows 上,如果您使用 gitk 或 git-gui,您将无法提示,因为您在 exec 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 !"

It works fine when running git commit from command line, but on Windows, 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.

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

.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 !"
顾北清歌寒 2024-09-20 05:55:32

如何在 Node.js 或 TypeScript 中执行此操作

编辑: 我制作了一个 npm 包


我看到人们在 Eliot Sykes 中评论如何为其他语言执行此操作回答,但是JavaScript解决方案有点长,所以我会单独回答。

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

我改编了这个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);

How to do it in Node.js or TypeScript

EDIT: I made an npm package


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.

I'm not sure if O_NOCTTY is 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_NOCTTY on, you wouldn't be able to send a CTRL+C to 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.

I adapted the answer from this GitHub issue

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

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

Anyway, I wanted to use this with Husky.js and it seems to work so far. I should probably turn this into an npm package when I get the 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);
独﹏钓一江月 2024-09-20 05:55:32

要使 select 停止输入,您还可以尝试从 /dev/fd/3selectstdin code> (参见:在 while 循环内读取 bash 中的输入)。

# 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

To make select stop for input, you may also try to redirect the stdin of select from /dev/fd/3 (See: 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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文