【问题标题】:git pre-commit hook code formatting with partial commit?git pre-commit 钩子代码格式化与部分提交?
【发布时间】:2014-05-16 12:49:03
【问题描述】:

有没有办法让预提交挂钩自动格式化代码(对于 以astyle 为例,但会破坏部分提交吗?

工作流程:

# edit a file.txt
git add -p file.txt
# add one chunk, but not another

git commit -m 'a message'
[PRE_COMMIT_HOOK] Formatting source code

git status
# the "another" chunk is still not added

我的问题是,如果您在 pre-commit 钩子中执行 git add,即 脚本格式化源代码后需要,添加“另一个”块, 也。但我不想那样。

有没有办法做到这一点?

【问题讨论】:

    标签: git formatting githooks pre-commit-hook


    【解决方案1】:

    有(某种)一种方法可以做到这一点。我不会,但如果你真的想这样做,请按照这些思路进行。

    首先,您需要将现有的两个项目分开:

    • 阶段性变化
    • 未暂存的工作树项

    此外,您希望第一组可用于重新格式化。

    这可以通过 git stash 完成,正如我在对 How do I properly git stash/pop in pre-commit hooks to get a clean working tree for tests? 的回答中所展示的那样(不过,请参阅那里关于 git stash 中的错误的警告)。

    基本上,您希望到达脚本中运行测试的位置:

    # Run tests
    status=...
    

    一旦处于这种状态,您可以通过格式化程序运行工作树项目,git add 将结果放入预提交挂钩中(正如您已经发现的那样)。这将避免格式化“另一个”块,因为它不在工作目录版本中。然后,您可以让提交继续进行(即,脚本的其余部分不适用于此处)。

    现在的问题是从存储中恢复工作树版本。因为您修改了索引,所以即使在提交完成后也无法返回:

    # Restore changes
    git reset --hard -q && git stash apply --index -q && git stash drop -q
    

    相反,您想要找到隐藏索引 (stash^1) 和隐藏工作树 (stash) 之间的差异,并将其应用于新的 HEAD 提交。在不使用 git 管道命令的情况下,至少有两种方法可以做到这一点。由于重新格式化提交的版本,两者都可能导致冲突:

    1. git diff stash^1 stash | git apply --reject(最终是git stash drop
    2. git stash branch tempbranch; git commit -m for-cherry-pick; git checkout prev-branch; git cherry-pick -n tempbranch; git branch -D tempbranch

    方法 1 更简单但很混乱,因为会导致合并冲突的更改会被放入“拒绝”文件中。方法 2 使用合并机制,因此如果需要,更改会获得冲突标记。 (如果没有冲突,-n 会阻止提交,这样您就可以自己处理真实的消息,而不是复制虚拟的 for-cherry-pick 消息。)

    当然,我还没有测试过这些。此外,还有一些方法可以在不使用git stash 的情况下执行此操作,例如将git add-ed 文件的索引版本检出到单独的目录中,在那里格式化内容,然后将格式化的版本添加回来,这样就没有此过程会影响当前工作目录。无论如何,如果您真的决心这样做,这可能会更好。这是 that 方法的脚本(也没有经过真正的测试——它需要添加一些健壮性,可能使用 -zxargs -0 来处理包含空格的文件名,在 diff 的检出中-index 输出部分):

    # make a directory for formatting files
    WORKDIR=$(mktemp -d -t reformat) || exit 1
    # clean it up when we leave
    trap "rm -rf $WORKDIR 0 1 2 3 15"
    # for files Added or Modified in the index, copy them to $WORKDIR
    git --work-tree=$WORKDIR checkout -- \
        $(git diff-index --cached --name-only --no-renames --diff-filter=AM HEAD)
    # reformat files in the work-dir
    (cd $WORKDIR; ...)
    # for each file in the work-dir, re-"add" that version to this tree
    # (this assumes the reformatter did not leave extraneous files!)
    git --work-tree=$WORKDIR add --ignore-removal .
    

    这是我的建议:与其在预提交挂钩中格式化代码,只需检查 它是否 已格式化。如果是这样,则允许提交。如果不是,拒绝它。这更符合预提交钩子的精神,它允许使用that other answer 中的脚本。基本上,在它说:

    status=...
    

    您只需运行一些东西来检查格式化程序是否会改变任何东西(也许通过允许格式化程序做它的事情,并查看结果是否与要提交的索引中的不同)。这让你得到你的地位。然后您执行脚本中的其余内容,使用 git reset --hard -q && git stash apply --index -q && git stash drop -q 将所有内容恢复到创建存储时的状态。

    【讨论】:

      【解决方案2】:

      我会通过使用低级“管道”命令来完成这项工作,我的第一次尝试将类似于

      git ls-files --stage \*.c | while read mode object stage path; do
      case $mode in
      10*)
            formatted=`git show $object | indent | git hash-object -w --stdin`
            git update-index --cacheinfo $mode $formatted "$path"
      ;;
      esac
      done
      

      为避免冗余处理,请按照@torek 的建议从git diff-index --name-only --diff-filter=AM 输出开始。

      【讨论】:

      • "--staged" 应该是 "--stage" 并且 "--cache-info" 应该是 "--cacheinfo"。
      • 现在呢?我想在那之后工作区必须进行更新,对吧?编辑:但到目前为止看起来真的很好!
      • Aahhh,下一步是通过indent 程序传送“真实”文件。这将是一种解决方案,但我不太喜欢它。我希望您理解我认为缺少的内容:indent 程序编辑的行在存储库中已更改,但在工作区中未更改。如果我通过indent 管道传输真实文件,我应该得到与我预期相同的结果。
      • 修正了拼写,谢谢。对于也做工作树副本,您的编辑器不是已经设置为每次都这样做吗?我就是这样做的,我认为这是add --patch标签损坏的后盾,如果不是这样,我会和@torek在一起:“而不是格式化在 pre-commit 钩子中的那个点的代码,只需检查它是否被格式化。如果是,则允许提交。"
      猜你喜欢
      • 2017-05-15
      • 2014-02-15
      • 1970-01-01
      • 2021-03-19
      • 1970-01-01
      • 2015-07-21
      • 1970-01-01
      • 1970-01-01
      • 2020-05-21
      相关资源
      最近更新 更多