仅获取基分支中不存在的提交范围



我只想提取不在其基础branchB中的branchA的提交。

例如,考虑一下这个历史:

B1 - B2 - B3 - B4 - B5

A1 - A2 - A3

我想只提取A1A2A3。需要注意的是,我不知道哪一个提交是A1,也不知道需要获取多少个提交。我的输入只是两个分支的头,在该示例中为CCD_ 7和CCD_。基于这样的输入,我需要识别A1,并获取A1branchA之间的所有内容,理想情况下仅此而已。

或者,获取包括A1A2A3的最小提交集,以及足够的信息来识别A1,也可能很有趣。

为什么?在我只需要这些提交的用例中("branchA相对于branchB发生了什么变化"),获取超过必要提交的数据会减慢我的进程。举个例子,一个有数千个提交的大型存储库,以及只有几个提交的功能分支。获取branchAbranchB的整个历史会获取很多我不需要的提交,并且需要大量的时间和网络带宽。

我想出了一个丑陋的破解方法,通过从浅层克隆开始,并逐渐获取越来越多的历史记录,直到找到一个常见的提交:

git clone --depth 1 "$repo" --branch "$branchA" shallow
cd shallow
for ((depth = 8; depth <= 1024; depth *= 2)); do
echo "trying depth $depth ..."
git fetch --depth $depth
git fetch --depth $depth origin "$branchB:$branchB"
lastrev=$(git rev-list --reverse "$branchB" | head -n1)
if git merge-base --is-ancestor "$lastrev" HEAD; then
echo "found with depth=$depth"
break
fi
done

这适用于我的用例:它获取足够大的提交子集,以识别A1并包括提交,直到branchA的头部,而且它比获取两个分支的完整历史记录更快。

还有比这更好的方法吗?我正在寻找一个纯粹的Git解决方案,但如果GitHub API有一些东西可以让它更快、更容易,那也很有趣。

这在今天是不可能的。你周围工作的变体是你能做的最好的。

协议中没有任何内容可以阻止您向git fetch提供原始哈希ID,而不是--depth参数,这将告诉git fetch假装提供了正确的--depth(无论是什么)。但是git fetch中也没有实现这一点。因此,实现这一点的唯一方法是从每个分支提示向后枚举提交,每次一个,直到找到正确的哈希,这也会"告诉"您git fetch命令的--depth参数应该是什么。

然而,当您迭代了足够多的哈希ID以找到正确的深度时,在大多数情况下,您可能已经完成了完整的克隆。因此,在Git之外(例如,通过GitHub接口)实现这一功能的压力非常小。而且,通过hash ID命名提交对人类来说也一点乐趣都没有——所以在git fetch中添加这个功能也没有什么压力。

最好的解决方案是,你可以向另一个Git存储库提供一个起始哈希(你自己的Git可以通过本地名称到哈希转换提供):如果你最后一次看到他们的B分支的顶端是,比如说,B4,这样你自己的origin/B就可以识别提交B4,run(注意,这个提议的--depth-inferred-from参数今天不存在):

git fetch --depth-inferred-from=origin/B A

哪个会有你的Git:

  1. 运行git ls-remotegit fetch始终运行的等效程序
  2. 将它们的refs/heads/A(您打算获取)转换为哈希ID,在步骤3中表示为H
  3. 要求他们的Git在have会话期间提交时仅枚举<hash-of-B4>..H
  4. 进入正常提取的剩余部分,即获取要提取的对象ID的have/wwant会话

然而,步骤3需要在fetch协议中添加一个新功能,因此非常不平凡。

解决方案1:使用--shallow-exclude=

git clone --shallow-exclude="$branchB" --single-branch --no-tags 
-b "$branchA" "$repo" shallow
cd shallow
git fetch --shallow-exclude="$branchA" origin "$branchB:$branchB"
# At this point, B3 itself would still be missing,
# so we have to add one more commit into the history of both branches.
git repack -d # Workaround for a bug. https://stackoverflow.com/q/63878612/4967497
git fetch --deepen=1 origin "$branchA" "$branchB"

不幸的是,如果您至少合并了一次这两个分支,这将无法按预期工作。考虑以下场景:

B1 - B2 - B3 - B4 - B5 - B6    branchB (e.g. master/main)
         
A1 - A2 - A3 - A4  branchA (e.g. your feature branch)

在浅签出中,branchBB5处停止,这意味着任何进一步的命令(如merge)都不会将B3视为branchB的一部分。

解决方案2:向.git/shallow添加碱基

如果您知道B3($base)的提交哈希,则可以执行以下操作:

echo "$base" >> .git/shallow
git fetch -n origin "$branchA:$branchA"

命令git fetch只下载提交到.git/shallow中的散列。请注意,如果您已经合并了几次分支,则必须将所有合并的提交添加到branchB中。考虑以下场景:

C1 - C2                 some merged branch
/       
B1 - B2 - B3 - B4 - B5 - B6    branchB (e.g. master/main)
         
A1 - A2 - A3 - A4  branchA (e.g. your feature branch)

如果您只将B3添加到.git/shallow中,git fetch仍然会下载B2和所有以前的提交,因为它们仍然可以通过B5->B4->C2->C1->B2

最新更新