git 列出已通过变基合并的分支

sbdsn5lh  于 2022-12-21  发布在  Git
关注(0)|答案(2)|浏览(160)

我有一个仓库,它有很多很多分支,这些分支已经通过github PR进行了rebase合并。即使合并分支到master是一个快速的过程,github也坚持做一个真正的rebase(将按下"合并"按钮的人作为所有提交的作者)。
我剩下的是这样的:

* (E) [master]
|
* (D')
|
* (C')
|
| * (D) [feat]
| |
| * (C)
|/
|
* (B)
|
* (A)

feat被合并之后。
我正在寻找一种通过编程来识别这些"几乎合并"的分支的方法,这样我就可以批量清理它们,就像git branch --all --merged master显示实际上已经合并(或者,当然,快进到)的分支一样。
使feat "几乎合并"并符合清理条件的feat属性包括:

  • featmaster之间,所有提交都有一个"并行"、"几乎相同"的提交(在这个例子中,CC'DD')。
  • 几乎相同是指:
  • 相同差异
  • 相同的提交消息
  • 相同的作者和作者日期(提交和提交日期不同)
  • 并行意味着:
  • feat和其在master上的共同祖先(在本例中为B)之间不存在在master上没有几乎相同的对的提交
  • master上几乎相同的提交以相同的顺序出现,并且紧跟在共同祖先(这里还是B)之后。

下面的图表表示了在这个定义下我不会认为是"几乎合并"的分支:
x一个一个一个一个x一个一个二个一个x一个一个三个一个
我可能会使用GitPython编写一个脚本来完成这一任务,但我希望已经有一些东西可以满足我的需要。

限定符

  • 我知道github有一个"自动删除头分支"的选项,我现在在这个仓库上启用了这个选项,但是有很长一段时间没有启用,因此有数百个"几乎合并"的分支需要清理
  • 如果有人知道有什么方法可以让github在合并公关时实现真正的快进,我将永远感激你(我会在ko-fi上给你买杯咖啡或类似的东西)
beq87vna

beq87vna1#

Edit: now that git range-diff exists, see sschuberth's answer.
实际上,我自己也想要一个这样的命令有一段时间了,所以我最终真的写了它。最终的结果,加上清理和选项处理,是here。它只是刚刚经过测试,可能有bug;正如他们所说,没有任何担保。
它只测试您命名的一个分支,而不是测试所有分支,但是它应该很容易修改。

脚本的解释以及其中的代码

通过比较提交的原始部分,可以很容易地测试"几乎合并"的情况:副本的所有部分都是一样的,除了父提交,当然还有你提到的提交器部分(甚至父提交在第一个/最后一个被复制的提交中也是一样的,第一个/最后一个取决于你对它们的看法)。
即:

* (E) [master]
|
* (D')
|
* (C')
|
| * (D) [feat]
| |
| * (C)
|/
|
* (B)
|
* (A)

我们只是将额外的提交(E)堆叠在字面上是彼此的副本的提交之上,除了有时parent行:D' = D,除了父节点,以及C' = C(在这种情况下,* 包括 * 父节点)。
你有这样一个图表,你称之为 * 非 *"几乎合并":

* (E) [master]
|
* (D')
|
* (C')
|
* (X)
|
| * (D) [feat]
| |
| * (C)
|/
|
* (B)
|
* (A)

这里的问题是提交X位于BC'之间,如果提交X字面上为空-或者是一对提交QR,其中RQ的还原-则C'将具有与C相同的树。这在下面的代码中很重要,因为我们可以测试C的父对象,或者直接查看是否有这样的提交。如果有这样一个提交并且它不是空的,或者如果有提交没有被恢复,那么树就不会匹配,我们会认为这个分支是"非几乎合并的"。如果提交是空的或者被恢复了,那么它就没有任何作用,至少我会很乐意称之为"几乎合并的"。
为了测试两个提交是否除了提交者的东西和父哈希值之外都相同,我们只需要在去掉这些行之后比较它们的Git内部对象内容,我们可以使用一个在shell中运行的sed脚本来完成:

trimcommit() {
    git cat-file -p $1 | sed -e '1,/^$/{/^committer /d;/^parent /d;}'
}
commiteq() {
    trimcommit $1 > /tmp/1
    trimcommit $2 > /tmp/2
    cmp -s /tmp/1 /tmp/2
}

这粗暴地使用了硬编码的临时文件名,而它并不清理这些文件名;我稍后在真实的脚本中修正了这个问题。它还有一个小缺陷:它删除了 * 所有 * 父行,而没有检查它是第一个父行还是其他父行。所以在一些不太可能的情况下,这可能会被合并提交所欺骗。不过我不会费心去修复这个bug。
因此,给定两个任意的分支名称M(主线)和F(可以合并到主线中的特性),我们现在可以这样开始:

# clean up on exit
trap "rm -f /tmp/left /tmp/right /tmp/1 /tmp/2" 0 1 2 3 15

# Obtain hash IDs on left (mainline) and right (feature) sides.
git rev-list --reverse --topo-order --left-only M...F > /tmp/left
git rev-list --reverse --topo-order --right-only M...F > /tmp/right

# Make sure both sides are nonempty, otherwise we can't really do anything.
if [ ! -s /tmp/left -a ! -s /tmp/right ]; then
    echo cannot help you - check your inputs
    exit 2  # signaling failure to test
fi

# Open the two files for input, and grab the first hash ID from each.
# The reads should succeed because the files are nonempty.
exec 3< /tmp/left 4< /tmp/right
read l <&3
read r <&4

# Look for a mainline commit that matches the topmost feature commit.
# This is our starting point to decide whether F is "almost merged" into M.
while ! commiteq $l $r; do
    # Drop the top-most left-side commit by reading another.
    if ! read l <&3; then
        echo "not almost-merged: right-side commits are missing"
        exit 1
    fi
done

此时,我们有两个选项,可以通过计算两个文件/tmp/left(nl)和/tmp/right(nr)中剩余的行数来区分,包括$l和$r中的当前提交哈希。注意,因为我们检查了空文件,所以它们都至少为1。

  • nl〈nr:F不被合并,因为剩下的提交太少了。
  • nl〉nr:F只有在允许空序列或no-op序列(空的X提交或Q后接reverts-Q R,在上面的思想过程中)时才能几乎合并。
  • nl ==数量

现在,假设我们允许nl〉nr,我们测试每个提交,如下所示:

# Topmost commits match, so drop them.  Read the remaining right side
# commits and require each left-side commit to match.
while read l <&3 && read r <&4; do
    if ! commiteq $l $r; then
        echo not almost merged
        exit 1
    fi
done

# If there are more lines in /tmp/left, there are some commits
# we're allowing.  We can check for that by trying to read again.
# Here, I don't bother.
echo is almost merged
exit 0
0md85ypi

0md85ypi2#

我刚刚编写了一个利用git-range-diff执行检查的脚本:

#!/bin/bash

UPSTREAM_BRANCH=${2:-origin/main}
REMOTE_BRANCHES=$(git branch -r -l origin/* | grep -vE "(HEAD|$UPSTREAM_BRANCH)")
CHECK_BRANCHES=${1:-$REMOTE_BRANCHES}

NUM_BRANCHES=$(echo "$CHECK_BRANCHES" | wc -l)
BRANCH_COUNTER=0

for BRANCH in $CHECK_BRANCHES; do
    BRANCH_COUNTER=$((BRANCH_COUNTER + 1))
    echo -n "[$BRANCH_COUNTER/$NUM_BRANCHES] Commits of branch $BRANCH "

    UNEQUAL_LINES=$(git range-diff $UPSTREAM_BRANCH...$BRANCH | grep -v " = " | wc -l)

    if [ $UNEQUAL_LINES -eq 0 ]; then
        echo "ARE contained in $UPSTREAM_BRANCH."
    else
        echo "are NOT contained in $UPSTREAM_BRANCH."
    fi
done

相关问题