GIT 从当前 HEAD 中删除特定提交的更改

GIT remove changes of specific commit from current HEAD

假设

我在索引中添加或未添加最近的更改。现在我正在挑选一个特定的提交,而不是在我的 HEAD 上创建一个新的提交 ...

git cherry-pick -n <commit>

如何从索引中删除精选更改?我可以做一个

git reset HEAD

但我必须重做之前添加的所有更改。


目的

如果一个人做了一个藏匿藏藏不能被推送到远程。当前的 WIP 无法从另一个系统上的远程系统中提取以供使用。所以我写了 shell 函数来模拟 git-stash 除了我为每个 stash 使用分支。

applypop 通常会将隐藏的更改应用到 WIP 而不是当前索引。当我使用 cherry-pick 应用来自 stash 分支的更改时,所有这些更改都将添加到索引中,之后我需要将它们从索引中删除。


编辑 (2018-01-29)

我看了@torek 的回答,明白了。尽管如此,我还是喜欢分享我以前使用过的 bash 功能。

function git-stash {
    local gitbranch="$( git branch | grep \* )"
    local currentbranch="$( [ "${gitbranch}" == "* (HEAD"* ] && echo "${gitbranch}" | cut -d ' ' -f5 | cut -d ')' -f1 || echo "${gitbranch}" | cut -d ' ' -f2- )"
    local stashname="stash/$( date +%s )"
    git stash save -u ${stashname}
    git checkout -b ${stashname}
    git stash pop
    git add .
    [  ] && git commit -m "WIP: " || git commit -m "WIP"
    git checkout ${currentbranch}
}

function git-stash-apply {
    local stashbranches="$( git branch | grep stash/ | cut -d ' ' -f3- | sort -r )"
    local stashbranches=(${stashbranches[@]})
    local lateststashbranch="${stashbranches[0]}"
    git cherry-pick -n "${lateststashbranch}"
}

function git-stash-pop {
    local stashbranches="$( git branch | grep stash/ | cut -d ' ' -f3- | sort -r )"
    local stashbranches=(${stashbranches[@]})
    local lateststashbranch="${stashbranches[0]}"
    git cherry-pick -n "${lateststashbranch}"
    git branch -D "${lateststashbranch}"
    git push origin :"${lateststashbranch}"
}

这还不是一个合适的解决方案,更不用说 stash pop 中缺少的错误处理了。

眼前的问题

在这种特殊情况下,您可以尝试 git revert -n <commit>,这与反向应用这些更改基本相同。不过,一般来说,这不是一个可逆操作,一开始就执行 git cherry-pick -n 是不明智的。

例如,考虑一下如果通过以下方式计算的增量会发生什么:

git diff $commit^ $commit

git cherry-pick $commit 应该在 README 中添加一行,从 f1.txt 中删除一行,并在 f2.txt 中更改一行("change"由删除旧添加新暗示,真的)。

但是如果您已经将该行添加到README并将该更改更改为f2.txt,实际上运行宁樱桃-pick 只会修改 f1.txt。 (发生这种情况是因为 Git 使用其合并机制,它将发现您的更改和它们的更改重叠,从而减少重叠。)如果您现在决定取消 cherry-pick 和 运行 git revert -n $commit、Git 将通过删除 README 中的行、将行添加回 f1.txt 并恢复 f2.txt 中的原始行来撤消它。 Git 不会 知道合并操作删除了这三个更改中的两个 "already in place",并将撤消所有三个。

更普遍的问题

If one does a stash the stash cannot be pushed to the remote.

这不完全正确(但也不完全错误)。 git stash 所做的是进行两次或有时三次提交,其中 none 在一个分支上:一个存储当前索引,一个存储当前工作树(但仅适用于那些在当前索引中)。如果第三次提交存在,它存储未跟踪的文件减去忽略的文件,或未跟踪的文件包括忽略的文件。

提交的安排使得工作树提交是最后一个提交,并且将另外两个(加上当前提交)作为其父项。然后最终提交的哈希 ID 是 "pushed onto" 使用 git update-ref.

的存储堆栈

因为这些是提交,所以可以 git push-ed。但是,您必须在遥控器上为它们起一个名称,遥控器将允许您设置该名称。 refs/stash 名称一般不可写。例如:

git push fred stash:refs/heads/sneaky

将使用 refs/stash 在远程 fred 上创建分支名称 sneaky

可以像上面那样用另一个名字发送提交,然后——通过登录到另一个系统——如果你想这样做的话,将它们偷偷带入 refs/stash 名字。不过,您甚至不必这样做,因为 git stash apply 等将采用任何解析为 "stash-like" 提交的标识符(特别是工作树提交,如果stash 是一个两次提交的实体,如果 stash 是一个三次提交的实体,则为三个父实体):

fred$ git stash apply sneaky

如果一切顺利,你不想再这样了,你可以强行删除分支:

fred$ git branch -D sneaky

另一篇技术说明

An apply or pop normally would apply the stashed changes to the WIP but not the current index.

这也是正确的,除非您使用 --index 选项。在这种情况下,隐藏代码尝试使用 git show <index-commit-hash> | git apply --index.

恢复索引

应用这样的存储会调用内部 Git 合并机制,尽管通常您可以通过使用 git apply -3 获得非常相似的效果。 (请注意,这与实际的 cherry-pick 或 merge 或 stash 调用之间存在细微差别。)