【问题标题】:What to stage after a pre-commit hook was run?运行预提交钩子后要执行什么?
【发布时间】:2011-12-14 14:44:02
【问题描述】:

对于修改暂存文件的预提交挂钩,我需要弄清楚在运行挂钩后必须暂存的内容。

预提交钩子在应该提交的文件上应用了一些漂亮的打印。该钩子执行以下任务:

  • 用空格替换制表符
  • 删除行尾的尾随空格
  • 删除双空行*)
  • 如果缺少,在文件末尾添加一个空行*)

标有 *) 的操作是导致下述问题的操作。

完成这些之后,钩子使用git add $filename 将修改后的文件添加到索引中。这样,整个文件就会被暂存,我不再能够只提交修改后文件的一小部分(即大块)。

当然,我可以git add --no-verify 绕过钩子,但使用git gui 时该选项不可用。另外,我想要在文件的分段行上应用漂亮的打印,所以绕过钩子不是我的目标。

有没有办法在应用漂亮打印后找出必须添加到索引中的内容,以便我可以在运行钩子后暂存 right 内容,而不是暂存整个文件?


编辑 1:
虽然David Brigada 提供的answer 看起来很有希望,但它不起作用:git stash --keep-index 保持分阶段的变化完好无损(这是好的部分),但是(至少在 msysgit 上)它把 all 更改为 stash(不仅是未暂存的)。这会在将存储弹出回 WC 时导致合并冲突,因为暂存的行可能会被修改。


编辑 2:
David 的更新答案也没有成功,因为 git 拒绝将存储合并到脏 WC。


编辑 3:
来自larsksanswer 指向了.gitattributes 的用法。乍一看,这似乎是正确的,但我发现签入的版本是通过过滤器运行的,而 WC 与签入的版本不同,这很令人困惑。至少,这是我的经验,Git Book 中的以下评论支持:

如果您提交这些更改并再次签出文件,您会看到正确替换的关键字

我必须删除文件,然后再次检查才能查看过滤器应用的更改?没门!还有更多提示吗?

【问题讨论】:

    标签: git hook pre-commit


    【解决方案1】:

    我不确定预提交挂钩是否适合执行此类工作。 Git 有一个过滤机制,允许您通过.gitattributes 将提交/签出过滤器应用于文档; Pro Git Book 包含使用此过滤机制将indent 程序自动应用于 C 源文件的示例。

    【讨论】:

    • 这似乎是实现我想要的另一种方式,但我对可用性有些担忧。查看更新的问题。
    • 感谢您的回答。尽管答案并没有真正帮助我解决问题,但我会将赏金奖励给您。我认为,没有办法做我想做的事,但你的回答将我指向.gitattributres,在你回答之前我不知道。很遗憾,我无法接受答案...
    【解决方案2】:

    您可以将尚未暂存以进行提交的更改放在预提交脚本中的存储区中,然后在完成后将其弹出。 --keep-index 选项仅存储您尚未添加到索引中的更改(尚未运行 git add on),--quiet 选项抑制您正在创建和销毁存储的通知。

    您描述的方式并不是规范的“git 方式”,我见过的大多数预提交脚本只检查不正确的样式,然后在发现任何内容时出错。这样,您就不会因为某种原因(例如测试数据)而让您的预提交脚本无意中重写了一些应该在其中包含选项卡的文件——您可以简单地使用 --no-verify 重新运行提交确信错误应该存在。

    我在这里有一个简单的预提交脚本。它几乎只是运行 git 对 diff-index --check 的基本测试,但它使用 git stash 只处理它应该进行的更改。

    #!/bin/sh
    if git rev-parse --verify HEAD >/dev/null 2>&1
    then
        against=HEAD
    else
        # Initial commit: diff against an empty tree object
        against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
    fi
    
    git stash save --keep-index --quiet
    git diff-index --check --cached $against --
    RETVAL=$?
    git stash pop --quiet
    exit $RETVAL
    

    更新:

    如果您尝试将此策略与修改您要提交的内容的脚本一起使用,您将遇到合并冲突。我们应该从 pre-commit 脚本中取出 stash pop,并在 post-commit 脚本中执行显式合并。

    #!/bin/sh
    
    git stash save --keep-index --quiet
    # Add your script to prettify the code here ...
    git add .
    

    现在提交后脚本如下所示

    #!/bin/sh
    
    git merge stash@{0} -s recursive -Xtheirs
    git stash drop --quiet stash@{0}
    

    recursivetheirs 参数合并应尽可能保留脚本所做的更改。

    【讨论】:

    • 虽然这个答案看起来很有希望,但它不起作用。 stash --keep-index 将暂存的更改保持不变,但(至少在 msysgit 上)它将 all 更改放入存储中(不仅是未暂存的更改)。这会在将存储弹出回 WC 时导致合并冲突,因为暂存的行可能会被修改...
    • @eckes:我将 stash pop 更改为显式合并,试图保留脚本所做的更改。看看这对你有没有帮助。
    • 感谢您的编辑。看起来更有希望,但也无济于事:error: Your local changes to the following files would be overwritten by merge: path/to/foo.h Please, commit your changes or stash them before you can merge.
    【解决方案3】:

    我不是 100% 确定我理解目标,但也许您可以尝试以下方法。让您的钩子生成补丁,而不是将更改写入磁盘。 (您可以这样做,例如,在 Python 中使用 difflib 模块,或者编写一个临时文件并输出到 diff。)然后使用 git apply(使用 --cached 和其他适当的标志)应用您的修补到索引以及工作树(手动或通过钩子自动)。

    【讨论】:

      【解决方案4】:

      由于我的目标似乎无法实现,我帮助我提出了另一个解决方案:

      我只在git gui 中暂存部分文件。幸运的是,git gui 允许您将自定义命令添加到 Tools 菜单。我设置了一个名为hooks.sh 的脚本,它接收一个参数,enabledisable。此脚本添加到Tools 菜单,一次以enable,一次以disable 作为参数。

      这样,我可以在暂存文件的一部分时轻松禁用挂钩,然后提交,然后再次启用挂钩。

      你去吧:

      #!/bin/sh
      
      ################################################################################
      # hooks.sh                                                                     #
      # enable or disable git hooks.                                                 #
      ################################################################################
      
      THEHOOK=pre-commit                  # which hook to work with
      ENABLEDFILE=./.git/hooks/$THEHOOK   # original name of the file
      DISABLEDFILE=$ENABLEDFILE.disabled  # disabled name of the file
      OLDFILE=''                          # for mv command: old name
      NEWFILE=''                          # for mv command: new name
      
      # parse parameters
      case $1 in
        disable)
          OLDFILE=$ENABLEDFILE
          NEWFILE=$DISABLEDFILE
          ;;
        enable)
          OLDFILE=$DISABLEDFILE
          NEWFILE=$ENABLEDFILE
          ;;
        *)
          echo -e "operation:\n    $0 enable  to enable the hook\n    $0 disable to disable the hook"
          exit 1
          ;;
      esac
      
      if [ -e $OLDFILE ]
      then
        mv $OLDFILE $NEWFILE
      else
        echo "nothing to do"
      fi
      

      【讨论】:

        【解决方案5】:

        你想要的是有一个 pre-commit 钩子来执行你描述的操作,然后一个 commit-msg 钩子。

        这就是我解决问题的方法:

        1. 添加以下预提交挂钩:

          #!/bin/sh
          git diff --cached --name-only -z --diff-filter=ACM |
                  xargs -r0 YOUR_SMUDGE_SCRIPT
          git diff --cached --name-only -z --diff-filter=ACM |
                  xargs -r0 git add -u -v --
          
        2. 添加以下 commit-msg 钩子:

          #!/bin/sh
          awk '!/^[[:space:]]*(#|$)/{exit f++}END{exit !f}' "$1" && exit
          # NOTREACHED unless commit was aborted
          git diff --cached --name-only -z --diff-filter=ACM |
                  xargs -r0 YOUR_CLEAN_SCRIPT
          git diff --cached --name-only -z --diff-filter=ACM |
                  xargs -r0 git add -u -v --
          

        注意将 YOUR_CLEAN_SCRIPT 和 YOUR_SMUDGE_SCRIPT 替换为您要运行的命令名,这将执行实际的清洁/涂抹例程。 smudge 例程应该扩展关键字(在提交前发生),而 clean 例程应该撤消更改(在提交中止时发生)。这些脚本将传递需要修改的文件(之前为不同的编辑暂存)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-03-30
          • 2018-07-22
          • 2021-09-10
          • 2014-05-13
          • 2011-07-02
          • 1970-01-01
          • 1970-01-01
          • 2014-08-10
          相关资源
          最近更新 更多