【问题标题】:Testing what is about to be committed in a pre-commit hook在预提交挂钩中测试将要提交的内容
【发布时间】:2012-09-07 14:36:35
【问题描述】:

pre-commit 挂钩运行时,存储库可能不干净。因此,如果您天真地运行测试,它们将不会与您正在提交的内容相冲突,而是与您的工作树中发生的任何事情相冲突。

显而易见的事情是git stash --keep-index --include-untrackedpre-commit 的开头和git pop 在结尾。这样您就可以针对(纯)索引进行测试,这正是我们想要的。

不幸的是,如果您使用 git add --patch(尤其是在您编辑 hunks 时),这会生成合并冲突标记,因为 stash@{0} 的内容在提交后可能与工作树不匹配。

另一种常见的解决方案是克隆存储库并在新的临时存储库中运行测试。这有两个问题:

  1. 我们还没有提交,所以我们不能轻易地获得我们即将提交的状态的存储库的副本;和
  2. 我的测试可能对当前工作目录的位置很敏感,例如由于本地环境配置。

如何在不引入合并冲突标记且不修改提交后HEAD 的情况下将我的工作树恢复到git stash --keep-index --include-untracked 之前的任何状态?

【问题讨论】:

  • 预提交脚本接收正在提交的数据作为输入。为什么你需要看其他东西?也许您尝试做的最好是在预提交钩子以外的其他东西中完成。您想要进行哪些需要访问完整存储库的测试?
  • @WilliamPursell:“正在提交的数据”是什么意思。预提交脚本在我的工作树(即源存储库的基础)中运行。问题是,如果您对存储库进行了一些更改并且只暂存了其中的一些(例如,您添加了一些文件但没有添加其他文件),那么您将不会在提交发生之前对其进行测试(我想要做的),您将测试工作目录中的所有内容。
  • 您提交的补丁在标准输入上可用于预提交挂钩。如果不是正在提交的补丁,你在测试什么?预提交挂钩的目的是验证补丁。
  • @WilliamPursell,你能详细说明一下吗?我的构建需要一秒钟左右。所以没有理由不运行它。如果您不认为这是对 pre-commit 的合法使用,那么您似乎暗示您知道在哪里。看看其他钩子,它们看起来不像我想要的。在这种情况下,我想防止事情被提交,除非他们建立。这似乎不是对pre-commit 的不合理使用。如果您认为是,请解释原因,以及可能的明智选择。
  • 我确实使用了很多小提交。我只想让代码处于易于一分为二的状态。我经常想在提交之前用git add -p 以简单的方式编辑帅哥。由于我的构建速度如此之快,我想确保这些调整不会破坏构建。

标签: git githooks pre-commit-hook pre-commit git-index


【解决方案1】:

git write-treepre-commit 挂钩中很有用。它将一棵树写入索引的 repo(如果提交完成,该树将被重用。)

将树写入 repo 后,您可以使用 git archive | tar -x 将树写入临时目录。

例如:

#!/bin/bash

TMPDIR=$(mktemp -d)
TREE=$(git write-tree)
git archive $TREE | tar -x -C $TMPDIR

# Run tests in $TMPDIR

RESULT=$?
rm -rf "$TMPDIR"
exit $RESULT

【讨论】:

    【解决方案2】:

    如果克隆整个 repo 过于昂贵,也许您只需要工作目录的副本。制作副本比尝试处理冲突更简单。例如:

    #!/bin/sh -e
    
    trap 'rm -rf $TMPD' 0
    mkdir ${TMPD=$PWD/.tmpdir}
    git ls-tree -r HEAD | while read mod type sha name; do
        if test "$type" = blob; then
            mkdir -p $TMPD/$( dirname "$name" ) 
            git show $sha > $TMPD/"$name";
            chmod $mod $TMPD/"$name"
        fi
    done
    cd $TMPD
    git diff --cached HEAD | patch
    # Run tests here
    

    这将转储树的状态,就像在 $TMPD 中提交之后一样,因此您可以在那里运行测试。您应该以比这里更安全的方式获得一个临时目录,但为了使最终的 diff 工作(或简化脚本和更早的 cd),它必须是工作目录的子目录。

    【讨论】:

      【解决方案3】:

      如果您负担得起使用临时目录(即制作当前结帐的完整副本),您可以使用临时目录,如下所示:

      tmpdir=$(mktemp -d) # Or put it wherever you like
      git archive HEAD | tar -xf - -C "$tmpdir"
      git diff --staged | patch -p1 -d "$tmpdir"
      cd "$tmpdir"
      ...
      

      这基本上是 William Pursell 的解决方案,但利用了 git archive 的优势,这使代码更简单,我希望会更快。

      或者,通过先cd'ing:

      cd somewhere
      git -C path/to/repo archive HEAD | tar -xf -
      git -C path/to/repo diff --staged | patch -p1
      ...
      

      git -C 需要 Git 1.8.5。

      【讨论】:

      • 我投了你一票,但它不满足“测试时留在 $PWD”,这对于某些系统是必需的,例如 go 库。
      • 除了 pwaller 的担忧之外,我相信如果您正在执行 git commit -a,此解决方案将不起作用,因为您的脚本假定我们仅提交暂存文件。
      【解决方案4】:

      我发现以下内容很有用:

      ## bash declare -a files readarray -t files < <(git status --porcelain | perl -ane 'print $F[1],qq(\n) if m/^[ACM] /') # declare -a delfiles readarray -t delfiles < <(git status --porcelain | perl -ane 'print $F[1],qq(\n) if m/^D /') # declare -a huhfiles readarray -t huhfiles < <(git status --porcelain | perl -ane 'print $F[1],qq(\n) if m/^\? /')

      调用git status 3 次可能效率低下,但此代码比调用一次、存储在内存中并循环结果要简单。而且我不认为将结果放入临时文件并从磁盘读取三遍会更快。也许。我不知道。这是第一关。随意批评。

      【讨论】:

        【解决方案5】:

        我终于找到了我正在寻找的解决方案。只检查提交前的索引状态,它使索引和工作树完全保持提交前的状态。

        如果您发现任何问题或更好的方法,请回复,无论是评论还是您自己的答案。

        这假设没有其他东西会在运行时尝试存储或以其他方式修改 git 存储库或工作树。这没有任何保证,可能是错误的,并将您的代码扔到风中。谨慎使用。

        # pre-commit.sh
        REPO_PATH=$PWD
        git stash save -q --keep-index --include-untracked # (stash@{1})
        git stash save -q                                  # (stash@{0})
        
        # Our state at this point:
        # * clean worktree
        # * stash@{0} contains what is to be committed
        # * stash@{1} contains everything, including dirt
        
        # Now reintroduce the changes to be committed so that they can be tested
        git stash apply stash@{0} -q
        
        git_unstash() {
            G="git --work-tree \"$REPO_PATH\" --git-dir \"$REPO_PATH/.git\"" 
            eval "$G" reset -q --hard             # Clean worktree again
            eval "$G" stash pop -q stash@{1}      # Put worktree to original dirty state
            eval "$G" reset -q stash@{0} .        # Restore index, ready for commit
            eval "$G" stash drop -q stash@{0}     # Clean up final remaining stash
        }
        trap git_unstash EXIT
        
        ... tests against what is being committed go here ...
        

        【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-02-10
        • 1970-01-01
        • 1970-01-01
        • 2019-06-02
        • 1970-01-01
        • 2010-12-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多