查找补丁所适用的第一个或最后一个提交记录

7
假设一个补丁是从过去的某个提交中创建的,并且不再适用于HEAD。如何找到补丁适用于“git apply”的历史记录中的第一个或最后一个提交?也许使用git bisect?但是哪个命令会告诉我补丁是否适用?理想情况下,我想回到那个提交,应用补丁,然后重新基于或合并原始HEAD,并进行差异比较以创建新的补丁(如果没有冲突)。之后,我想回到原始的HEAD,以便可以继续进行更多的补丁。背景:有一些需要重新滚动的补丁...(是的,还有一些生态系统仍然在使用补丁...)

git diffgit format-patch 创建的补丁在哪里? - user456814
2
找到了这个链接:https://dev59.com/nmDVa4cB1Zd3GeqPfaZC 现在我们只需要进一步封装它,以便可以自动化重新打补丁100次。 - donquixote
1
所以等等,有一个问题,git bisect 要求您传递一个“好的修订版”,在这种情况下,该修订版应用补丁而不产生冲突。由于这个原因,我怀疑 git bisect 并不总是一个好工具。 - user456814
1
顺便提一下,这是我的出处:https://drupal.org/node/2247991#comment-8819487 我还没有看到xjm的脚本,但想看看我会怎么做。 - donquixote
1
你知道你也可以使用 git log -S <search-string>git log -G <regex> 来查找第一次添加/删除 <search-string><regex> 的行,对吧?这可能会帮助你找到可能的“第一个提交”,其中可能生成了补丁。 - user456814
显示剩余11条评论
2个回答

2
这个答案假设补丁是用git diff创建的,而不是git format-patch,并且您在git log中使用的默认页面 less

以下是从git diff <sha1> <sha2>创建的补丁的示例:
diff --git a/osx/.bash_profile b/osx/.bash_profile
index c7b41df..fb80367 100644
--- a/osx/.bash_profile
+++ b/osx/.bash_profile
@@ -3,6 +3,10 @@
 # Setup PATH for Homebrew packages
 export PATH=/usr/local/bin:$PATH

+# Setup Scala variables
+export SCALA_HOME=/usr/local/Frameworks/scala # Symlinked directory
+export PATH=$PATH:$SCALA_HOME/bin
+
 # Initialize rbenv,
 # https://github.com/sstephenson/rbenv#homebrew-on-mac-os-x
 eval "$(rbenv init -)"

请看这行代码:

+export SCALA_HOME=/usr/local/Frameworks/scala # Symlinked directory

并在 git log --patchgit log -p 中搜索它。 在 less 中键入 /,然后输入要搜索的正则表达式:

/\+export SCALA_HOME=/usr/local/Frameworks/scala # Symlinked directory

这里使用\+进行转义,因为它在正则表达式中是一个特殊字符。按回车键可找到第一个匹配项,按n可以找到下一个匹配项,或者按N返回上一个匹配项。
这将帮助您找到可能是补丁来源的提交。您还可以在less中使用空格键向下翻页,按b向上翻页。

嗯,这可能有用,但它远远不能自动找到正确的提交,所以我无法批量重新滚动补丁。 - donquixote
我也想知道为什么补丁中的一行代码应该出现在日志中的任何地方?例如,如果补丁添加了“vanilla ice”,但HEAD的历史记录中没有这样的东西呢? - donquixote
1
@donquixote正在使用“git bisect”努力寻找更好的解决方案,大约需要10-15分钟时间。此外,你看过“git log -p”的效果吗?它可以将差异信息添加到日志中。 - user456814
git apply --check 看起来很有前途。 - donquixote
所以,如果 git apply --check 失败,它会打印出一些东西(“error ...”),但是如果补丁程序应用成功,则不会打印任何内容。现在需要将其与 git bisect 结合使用。 - donquixote
是的,“git log -p”很有趣,而且我以前不知道。谢谢! - donquixote

1

Git的做法

git apply --3way 命令应该使用blob哈希值来定位每个文件的基本版本,并在一步中向前合并所有内容,假设它们存在于您的存储库历史记录中,并且您可以处理合并冲突。对于许多人来说,这可能是一个更简单的解决方案。

按照您的要求进行操作的方法

如果您仍然真的想知道一个包含差异所来自的基本文件的历史提交记录,我的脚本将扩展查找包含单个blob哈希的提交记录的其中一种解决方案,以尝试查找包含从补丁文件中提取的一组blob哈希的提交记录。

#!/bin/sh
# git-find-patch-base takes a patch produced by "git diff" and tries to locate commit(s)
# containing all source blobs

# The first parameter is the name of the patch file to examine
patch_file="$1"
# Any remaining parameters are passed as a group to the git log command using $@ below
shift

# Make a temporary file and capture a list of all the starting
# file blob hashes that the patch used in it. Note: Adding a file shows
# a starting hash of 00000000, so we filter that one out...
tmp_blob_file=$(mktemp)
echo "Examining patch file \"$patch_file\"..." 1>&2
grep -E "^index" "$patch_file" | colrm 1 6 | colrm 10 | sort | uniq | grep -v 00000000 > "$tmp_blob_file"

# Count how many unique blob hashes we identified
blobcount=$(cat "$tmp_blob_file" | wc -l)
echo "Found $blobcount unique blob hashes in patch..." 1>&2

# Use git log to get a list of commits to check against. Then, for
# each of those commits, count how many of the blob hashes that we
# wanted appear in it, and output the commit hash if it's at least the
# ideal blob count. Note: this is an imperfect searching method, since
# there is a chance for hash collision, exacerbated since the grep is not
# forcing the short hashes to only match the beginning of the long
# hashes.
echo "Searching log/tree history of git..." 1>&2
git log "$@" --pretty=format:'%T %h %s' \
| while read tree commit subject ; do
    if test $(git ls-tree -r "$tree" | grep -f "$tmp_blob_file" | wc -l) -ge "$blobcount" ; then
        echo "$commit" "$subject"
        break
    fi
done

# Clean up the temporary file we made...
rm "$tmp_blob_file"

第一个参数是要分析的补丁文件的名称,其余任何参数都会传递给git log以帮助扩展/限制要检查的提交列表。如果您想要相对于特定分支的第一个提交,可以运行git-find-patch-base foo.patch branchname。如果您完全不知道某个东西来自哪里,可以运行git-find-patch-base foo.patch --all并在它完成时喝杯咖啡。git log有许多有用的限制器,例如--grep--author,可以加快此过程。

所示的脚本在while循环中使用break停止第一次匹配。您可以将其删除,这将彻底搜索所有候选提交。


我突然想到,搜索方法的不足之处可以通过对提交执行“git apply --check”来消除,但这也需要在脚本中进行“git checkout”... - JustinB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接