当交互式 Git 变基完成时,我如何 运行 命令?

How do I run a command when an interactive Git rebase is finished?

我有一个包装 git rebase --interactive 的 shell 脚本,我希望它在 rebase 完成后做一些事情。 (如果 repo 的初始内容和最终内容之间存在差异,我希望它通知用户。)但是,只写

是不可能的
git rebase --interactive some_commit
# ...check for differences here...

因为如果用户选择编辑任何提交,或者如果有任何冲突需要解决,那么 git 将在此时退出并且以下行将是 运行,尽管从概念上讲 rebase 还没有完成。

我想安装一个临时的“post-rebase”钩子,但是没有这样的钩子。 this question 的答案表明 post-rewrite 挂钩在某些情况下会起作用。但是,即使用户 运行 git rebase --abortgit rebase --quit,我也想清理挂钩,并且我假设 post-rewrite 挂钩在这些情况下不会触发。 (我不想安装永久挂钩来支持这个脚本,因为我希望脚本可以在任何 repo 上工作而无需事先进行任何设置。此外,我的一些 repo 已经使用 post-checkout hooks 来做不相关的事情。)

有没有办法在 rebase 完成时让 Git 到 运行 某个脚本?

解决方案是使用两个单独的脚本:一个启动 git rebase --interactive 的脚本(“包装脚本”)和一个 运行 在变基结束时的脚本(“ post-变基脚本”)。您可以在 rebase 待办事项列表中插入 exec 行,以确保后者在 rebase 结束时自动为 运行。此外,您可以设置 GIT_SEQUENCE_EDITOR 环境变量以自动添加 exec 行本身。

post-rebase 脚本

将变基结束时应 运行 的逻辑放入单独的脚本中。 (请注意,如果 rebase 被中止或退出,此脚本将不会是 运行。)如果脚本需要任何上下文而不是 rebase 期间环境中可用的上下文,它应该接受此信息作为命令行参数。

例如,我的 shell 脚本名为 check_git_diff,它看起来像

#!/usr/bin/env zsh

if [[ $# -ne 1 ]]; then
    print >&2 "usage: check_git_diff ORIGINAL_HEAD"
    exit 1
fi

readonly original_head=

if ! git diff --quiet $original_head HEAD; then
    kind='non-whitespace'
    if git diff --quiet --ignore-all-space $original_head HEAD; then
        kind='whitespace'
    fi
    print -P >&2 "%F{red}Warning:%f There are $kind differences from the original HEAD. See"
    print -P >&2 "\n  %F{blue}git diff $original_head HEAD%f\n"
fi

自动调用此脚本

您可以通过在待办事项列表末尾添加 exec 行,要求 Git 本身在 rebase 末尾 运行 此脚本。在所有 picksquashfixup 等行之后,为 post-rebase 脚本添加一个:

pick 8bbfd15 update build instructions
pick caf5bfd frobnicate some additional blobs

exec check_git_diff caf5bfd3d4b6099aeed13604936976e610a08e18

# Rebase 9f594d2..caf5bfd onto 8bbfd15 (2 commands)
#
# ...

如您所见,如果需要,您可以在 post-rebase 脚本中包含参数。

如果你 运行 git rebase --abortgit rebase --quit,Git 将停止 运行ning 这些命令,这就是为什么我在上面说 check_git_diff 只会在 成功 变基结束时成为 运行。

自动自动调用此脚本

由于首先启动的是您的脚本 git rebase --interactive,因此您有机会自动执行上一步。在包装器脚本中,在调用 git rebase --interactive 之前设置 GIT_SEQUENCE_EDITOR 环境变量。例如,

original_head=$(git rev-parse HEAD)
export GIT_SEQUENCE_EDITOR="f() {
    sed -i -e '/^$/a exec check_git_diff $original_head\n' $1 && exec vim $1
}; f"

如果设置了 GIT_SEQUENCE_EDITOR 环境变量,Git 将使用它的值作为编辑器的名称来编辑待办事项列表。通常你会将其设置为 vimemacs 之类的东西,但在这里我实际上定义了一个 shell 函数,然后将该函数用作编辑器。 (Git 使用 Bash 到 运行 GIT_SEQUENCE_EDITOR 的内容。) shell 函数做了两件事:它调用 sed 修改待办事项列表以包含 exec 行,然后是 运行s Vim 以便您可以像往常一样编辑待办事项列表。当然,您可以将 vim 替换为您喜欢的编辑器的名称。

最终效果是您可以在交互式变基开始之前运行一些特定的逻辑,然后在它成功结束后运行一些特定的其他逻辑。

感谢@torek 让我注意到 GIT_SEQUENCE_EDITOR 变量。这让解决方案变得简单多了!

我不确定我喜欢这个,但也许这个想法会激发某人写一些好东西...

git rebase -i 的包装器 shell 脚本可以按照

的方式做一些事情
git_rebase_in_progress() {
    [[ -d "$(git rev-parse --git-path rebase-merge)" || -d "$(git rev-parse --git-path rebase-apply)" ]]
}

if git rebase --interactive ...
then
    while git_rebase_in_progress
    do
        echo "Rebase is in progress, exit 1 to abort the rebase, or run the normal interactive rebase commands like 'git rebase --skip' or 'git rebase --continue"
        # TODO: Set the shell prompt to indicate the following is not a normal shell
        if ${SHELL}
        then
            echo "Successful rebase step by user, continuing the rebase"
            continue
        else
            echo "User has aborted the rebase."
            break
        fi
    done
    echo "Rebase has finished, running special commands"
    # special commands go here
else
    echo "The rebase has failed."
fi