git钩子:格式化并同时重新添加文件

13

我们目前使用以下 git hook 来运行 astyle 格式化源代码,然后才允许用户提交。但这种方法有个缺点,就是用户必须先提交代码,再格式化代码,最后再次提交,这有点麻烦。理想情况下,我们希望钩子可以格式化代码,并将已格式化的代码包含在原始提交中。我尝试重新添加更改后的文件,但会导致引用错误(显然)。我还尝试在 pre-commit 钩子中获取历史记录,退出钩子并重新运行 git commit 命令,但没有成功。

# Run astyle on changed .cs files, ignoring $ignored
res=$(exec git diff --cached --name-only | \
    grep -Ev $ignored | \
    xargs astyle --options=conf/astylerc | \
    tail -n 1)
num_formatted=$(echo $res | cut -b 1) # We are only interested in the number preceeding 'formatted'.
[[ $num_formatted -ne 0 ]] && echo "WARNING: Code has been automatically formatted. Please re-add and re-commit" && exit 1 || echo "No code to format! Continuing commit"

有人有什么想法吗?


为什么不在提交之前运行预提交钩子并格式化代码呢? - Michael
这是一个预提交钩子。只是我的开发人员需要在文件格式化后再次运行git add和git commit。所以看起来像:git add file.cs git commit -m "msg" 警告:代码已格式化 git add file.cs git commit -m "msg again"这很烦人。我更喜欢:git add file.cs git commit -m "msg" 代码已格式化,重新添加并提交!谢谢! - DTI-Matt
3个回答

11

根据this的回答进行了编辑。

你可以在钩子中格式化并添加回你的文件。问题是你可能有已暂存文件的未暂存修改。为了以清晰的方式完成这个操作,你可以将文件从索引中取出作为临时文件,对临时文件进行格式化,并用格式化后的临时文件替换索引中的条目。以下是一个解决该问题的方法:

# Regexp for grep to only choose some file extensions for formatting
exts="\.\(ext\|ext2\)$"

# The formatter to use
formatter=`which your_formatter`

# Check availability of the formatter
if [ -z "$formatter" ]
then
  1>&2 echo "$formatter not found. Pre-commit formatting will not be done."
  exit 0
fi

# Format staged files
git diff --cached --name-only --diff-filter=ACMR | grep "$exts" | while read file; do
  echo "Formatting $file"
  # Get the file from index
  git show ":$file" > "$file.tmp"
  # Format it
  "$formatter" -i "$file.tmp"
  # Create a blob object from the formatted file
  hash=`git hash-object -w "$file.tmp"`
  # Add it back to index
  git update-index --add --cacheinfo 100644 "$hash" "$file"
  # Remove the tmp file
  rm "$file.tmp"
done

# If no files left in index after formatting - fail
ret=0
if [ ! "`git diff --cached --name-only`" ]; then
  1>&2 echo "No files left after formatting"
  exit 1
fi

6
这个脚本不会格式化文件系统中的文件,这使得提交后的差异仍然包含未格式化的代码。我在格式化.tmp后面添加了:"$formatter" -i "$file"。不过,几乎肯定有更好的方法来解决这个问题。 - perpetual_check
git cat-file -p "$hash" >"${file}" - Linus Fernandes

10
在您的预提交钩子中,您需要添加您的文件。如果您的钩子类似于以下内容:
#!/bin/bash
echo 1 > file
exit 0

接下来,您需要修改它以添加:

#!/bin/bash
echo 1 > file
git add file
exit 0

要获取所有修改过的文件列表,您可以使用git-ls-files命令:
git ls-files -m
然而,最好的方法是从您的代码中获取已修改的文件列表或添加所有文件。使用 `git diff-tree -r --name-only --no-commit-id ` 命令可以为您获取所有文件的列表。
基本上,在修改后再次添加文件是有效的,因为提交直到预提交钩子运行之后才会发生,所以此时工作树中暂存的内容将被提交。

哇,你说得太对了。我不知道为什么我尝试从钩子提交...有时候你会被过度复杂化的问题所迷惑。无论如何,现在我运行格式化程序来处理所有需要它的文件,将其存储在一个$changed变量中,然后git add $changed。非常好用!谢谢! - DTI-Matt
你也可以使用 git add -u 命令来添加所有修改过的文件。 - undefined

4
你可以使用这里描述的技术仅存储未暂存的更改。然后对已暂存的更改运行格式化程序并弹出存储。下面的pre-commit钩子使用clang-format-diff
#!/bin/sh

# stash unstaged changes
git commit --no-verify -m 'Save index'
old_stash=$(git rev-parse -q --verify refs/stash)
git stash push -m 'Unstaged changes'
new_stash=$(git rev-parse -q --verify refs/stash)
git reset --soft HEAD^

# format staged changes
git diff -U0 --no-color --staged HEAD -- '*.java' | $PWD/clang-format-diff.py -i -p1

git add -u
if [ "$old_stash" != "$new_stash" ]; then # if unstaged changes were stashed reapply to working tree
    git stash pop
fi
exit 0

为了能够使用 git commit --amend 修改之前提交的摘要,我必须在第一个命令中添加 --allow-empty - Florent Bécart
当stash为空时,此脚本将失败。 - mozu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接