Git commit 遇到的 precommit Not a git repository 错误

问题描述

在提交时遇到执行 hooks 失败,错误提示“Not a git repository”
具体错误如下所示:

1
2
3
4
5
6
7
8
9
10
11
xyf$ git commit -m 'feat: test commit'
husky > pre-commit (node v8.11.3)

> [email protected] precommit /Users/lego
> lint-staged

  ? Running tasks for **.{js, es6, jsx}
Not a git repository
To compare two paths outside a working tree:
usage: git diff [--no-index] <path> <path>
husky > pre-commit hook failed (add --no-verify to bypass)

husky 的配置如下:

1
2
3
4
5
6
7
8
9
"husky": {
    "hooks": {
      "pre-commit": "npm run precommit && git diff",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  },
  "devDependencies": {
    "husky": "^1.0.0-rc.8"
  }

问题原因

参考网上资料,经定位是运行 hooks 时的 Git 环境变量 GIT_DIR 错误造成
https://stackoverflow.com/questions/3542854/calling-git-pull-from-a-git-post-update-hook

验证本地执行 hooks 时的 GIT_DIR=.git,正确的 GIT_DIR 应该为空(即未设置),在正常的电脑执行 hooks 的 GIT_DIR 确认为空

确认原因

确认 GIT_DIR 的值使用这两种方式,一是在 pre-commit hooks 配置里增加 echo $GIT_DIR,二是在 .git/hooks/pre-commit 文件增加打印 echo $GIT_DIR

方法一,hooks 配置增加 echo $GIT_DIR

1
2
3
4
5
6
"husky": {
    "hooks": {
      "pre-commit": "echo $GIT_DIR && npm run precommit && git diff --check --staged",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  }

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
xyf$ git commit -m 'fix: test comit 2'
pre-commit

husky > pre-commit (node v8.11.3)
.git

> [email protected] precommit /Users/xieyufang/Documents/projects/pt-lego/service
> lint-staged

  ? Running tasks for **.{js, es6, jsx}
Not a git repository
To compare two paths outside a working tree:
usage: git diff [--no-index] <path> <path>
husky > pre-commit hook failed (add --no-verify to bypass)

方法二,.git/hooks/pre-commit 文件增加 echo $GIT_DIR

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/bin/sh
# husky

# Hook created by Husky
#   Version: 1.3.1
#   At: 2020-7-15 22:10:18
#   See: https://github.com/typicode/husky#readme

# From npm package
#   Name: husky
#   Directory: /Users/lego/node_modules/husky
#   Homepage: https://github.com/typicode/husky#readme

echo $GIT_DIR;

scriptPath="service/node_modules/husky/run.js"
hookName=`basename "$0"`
gitParams="$*"

echo $hookName;
echo $gitParams;
debug() {
  [ "${HUSKY_DEBUG}" = "true" ] && echo "husky:debug $1"
}

debug "$hookName hook started..."

if ! command -v node >/dev/null 2>&1; then
  echo "Can't find node in PATH, trying to find a node binary on your system"
fi

if [ -f "$scriptPath" ]; then
  # if [ -t 1 ]; then
  #   exec < /dev/tty
  # fi
  if [ -f ~/.huskyrc ]; then
    debug "source ~/.huskyrc"
    source ~/.huskyrc
  fi
  service/node_modules/run-node/run-node "$scriptPath" $hookName "$gitParams"
else
  echo "Can't find Husky, skipping $hookName hook"
  echo "You can reinstall it using 'npm install husky --save-dev' or delete this hook"
fi

运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
xyf$ git commit -m 'fix: test comit 2'
.git
pre-commit

husky > pre-commit (node v8.11.3)

> [email protected] precommit /Users/lego
> lint-staged

  ? Running tasks for **.{js, es6, jsx}
Not a git repository
To compare two paths outside a working tree:
usage: git diff [--no-index] <path> <path>
husky > pre-commit hook failed (add --no-verify to bypass)

解决问题

即然发现是 GIT_DIR 错误造成,那就想办法更改,试验以下这几种方法

1
2
3
4
5
6
7
8
9
10
// 1 通过指定 --git-dir 参数改变当前执行的 Git 命令的环境变量
git --git-dir=.git {command}
// 2 重置 Git 运行的所有环境变量
unset $(git rev-parse --local-env-vars)
// 3 直接清除 GIT_DIR 的环境变量
unset GIT_DIR
// 4 Git 命令前追加 env -i 执行
env -i git {command}
// 5 移除 husky 配置中相关的 Git 命令
git diff

直接修改 hooks 的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 方法一:指定 --git-dir 参数,注意这里的执行 hooks 和 .git 的相对目录
// 这里执行 hooks 是在仓库的下一级目录,所以这里指定 ../.git
"husky": {
  "hooks": {
    "pre-commit": "npm run precommit && git --git-dir=../.git diff",
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
  }
}

// 方法二:重置 Git 运行环境变量
"husky": {
  "hooks": {
    "pre-commit": "unset $(git rev-parse --local-env-vars) && npm run precommit && git diff",
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
  }
}

// 方法三:直接清除 GIT_DIR 的值
"husky": {
  "hooks": {
    "pre-commit": "unset GIT_DIR && npm run precommit && git diff",
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
  }
}

// 方法四:在 Git 的命令前加上 env -i
"husky": {
  "hooks": {
    "pre-commit": "npm run precommit && env -i git diff",
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
  }
}

// 方法五:移除有关的 Git 命令
// 即然是 GIT_DIR 环境变量错误造成的执行 Git 命令错误,那直接移除相关命令也可以解决
"husky": {
  "hooks": {
    "pre-commit": "npm run precommit",
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
  }
}

经验证,以上几种方式都可以成功,完成 git commit

分析原因

上面的解决办法只是临时解决了问题,并没有找到问题的根本原因,为什么会有 GIT_DIR 的不同的值,一开始是怀疑执行 hooks 的环境造成,试图找出 hooks 怎么执行的,查找 Git Hook 和 husky 文档依然未发现如何执行,在接着试图通过比较正常和出问题的电脑的本地环境和配置找出它们的差异性,依然没有发现问题。

针对同一个仓库,比较它们 husky 的安装版本,比较它们 hooks 的命令,这些都没有差异

node_modules/husky
.git/hooks/pre-commit

再后来不经意看到 husky 文档提到的 hooks 不能正确执行的原因有一句话

If not, you may have another Git hooks manager defined in your package.json overwriting husky's hooks. Check also the output during install, you should see:

1
2
husky > Setting up git hooks
husky > Done

然后就从 npm install 的 husky 日志方向上入手,首先分别在正常和出问题的电脑上执行 npm i 查看结果没有差异,接着试着在出问题的电脑上新建 git 仓库在安装 husky 发现了差异点

当前仓库 npm i 的结果,husky hooks 安装成功

1
2
3
4
5
> [email protected] install /Users/lego/node_modules/husky
> node husky install

husky > setting up git hooks
husky > done

出问题的电脑,新仓库的 npm i husky 记录,husky hooks 安装失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
xyf$ npm install husky --save-dev

> [email protected] install /Users/pt/node_modules/husky
> node husky install

Husky requires Node 10 (runtime: v8.11.3), skipping Git hooks installation.

> [email protected] postinstall /Users/pt/node_modules/husky
> opencollective-postinstall || exit 0

Thank you for using husky!
If you rely on this package, please consider supporting our open collective:
> https://opencollective.com/husky/donate

npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN [email protected] No license field.

+ [email protected]
added 48 packages from 22 contributors and audited 48 packages in 8.645s
found 0 vulnerabilities

比较发现出问题原因是 [email protected] 依赖 node > 10,而本地环境只有 8.11.3 造成 husky hooks 安装失败,而安装成功的 husky 版本是 1.3.1 在当前的 [email protected] 版本下能安装成功

综合上面结果,比较出两台电脑的环境如下
[email protected] & [email protected]
[email protected] & [email protected]
虽然基于上面的环境 husky hooks 都能安装成功,但 [email protected] 下执行 hooks 失败,依然是因为 GIT_DIR = .git

在接着发现这个文章,Git 2.18.0 后不在设置 GIT_DIR
$GIT_DIR is no longer set when pre-commit hooks are called

As of the release of 2.18.0, $GIT_DIR is no longer set before calling pre-commit hooks. This change was introduced in "set_work_tree: use chdir_notify" (8500e0de) and is still present in master. I reviewed the discussion when this change was initially submitted, and I don't think this behavior change was intentional. I have several hooks that were broken like this, and from a quick Google search, using $GIT_DIR without setting a default does seem pretty common in hooks.

于是再查看两台机器的 Git 版本,一个是 2.15.1(有问题的机器),一个是 2.21.0(正常的机器),这解释了为什么在[email protected]机器(有问题的机器)上执行 hooks 时的环境变量 GIT_DIR 有值

在有问题的机器上新仓库实验 hooks 是成功的,但发现只要 node_module 和 .git 不是同一级目录就会出现这个错误

结论

造成问题的原因还是版本的问题相互影响造成,[email protected] + [email protected] + [email protected],基于这样的环境的可以成功安装 husky,但只能在 node_module 和 .git 目录同级才能成功执行 husky,如果 .git 和 node_module 在不同目录就会遇到当前的问题

附加

关于 git rev-parse --git-dir 命令的用法的解释

The "right" way to find the directory has always been "git rev-parse --git-dir" (which will use GIT_DIR if set, and otherwise do the normal discovery process).

参考资料

https://github.com/typicode/husky/issues/364
https://stackoverflow.com/questions/3542854/calling-git-pull-from-a-git-post-update-hook
http://jakescruggs.blogspot.com/2010/07/using-git-inside-git-hook.html
https://mirrors.edge.kernel.org/pub/software/scm/git/docs/git.html
https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
https://git-scm.com/docs/git-rev-parse
https://www.npmjs.com/package/husky#ci-servers
https://public-inbox.org/git/[email protected]/t/