首页 > 解决方案 > git rebase 显示在后续提交中已经解决的冲突

问题描述

我一直遇到我有 PR 的情况,需要合并来自 master 的更改并在新提交中修复冲突(而不是 rebase,因为其他人可能在同一个分支上工作),然后一旦 PR 获得批准,我需要压缩并重新提交提交。

这是我想要压缩/变基的提交布局

commit1: Merge branch master into this branch. Manually resolved conflicts in foo/file0 
commit0: Added feature.

当基于 master 重新定位时,它会到达 commit0 并检测到存在冲突,即使这些冲突在 commit1 中已解决。注意

#git rebase -i master

pick commit0 Initial commit

Could not apply commit0... Initial commit
Auto-merging foo/file0
CONFLICT (content): Merge conflict in foo/file0

我尝试每次都接受我的更改,而不是接受来自主人的更改;虽然这样做之后,我留下了已经在 commit1 中解决的冲突。

在无需重复我在 commit1 中执行的手动冲突解决方案的情况下,压缩和 rebase 提交的最佳方法是什么?

标签: git

解决方案


可以使用git rerere,也许,正如eftshift0 在评论中所建议的那样,但我认为先压缩然后变基更简单。

根据您希望它的花哨/聪明/可靠程度,它可能会有点棘手。我对此感到好奇并写了这篇文章。这是非常轻微的测试。不过,它可能没有您想象的那么有用。将它保存为一个名为 的文件git-squashbase,在你的某个地方,$PATH例如git squashbase master,在将 master 合并到当前功能并解决冲突之后运行。

#! /bin/sh

OPTIONS_KEEPDASHDASH=
OPTIONS_STUCKLONG=
OPTIONS_SPEC="git squashbase [options] upstream

Using the given upstream, first squash this branch, then rebase it.
--
i,interactive  run git rebase --interactive instead of just making one commit
onto=          passed to final rebase
"

# parse options (defined above) and obtain "die" function etc.
. git-sh-setup

# Turn anything acceptable to git rev-parse into a commit hash ID.
# If it's not a commit hash ID, or unacceptable, quit now.
get_commit()
{
    local hash=$(git rev-parse --verify "$1") || exit
    hash=$(git rev-parse "${hash}^{commit}") || exit
    echo $hash
}

interactive=false
onto=false; target=
while :; do
    case "$1" in
    --) shift; break;;
    -i) interactive=true; shift;;
    --onto) onto=true; target=$(get_commit "$2"); shift 2;;
    esac
done

case $# in 1) ;; *) usage; esac

# Require a clean working tree and index.
require_clean_work_tree squashbase

# Everybody remember where we parked...
orig=$(git rev-parse HEAD) || exit
have_sym_orig=true
sym_orig=$(git symbolic-ref -q --short HEAD 2>/dev/null) || have_sym_orig=false

# ... and where we're going.
upstream_orig="$1"
upstream=$(get_commit "$1")
$onto || target=$upstream

# Return to where we started.
go_home()
{
    if $have_sym_orig; then
        git checkout -q $sym_orig || exit
    else
        git checkout -q $orig || exit
    fi
}

# Bind original branch name to the current (HEAD) hash ID.
# Assumes index and working tree are in order.
set_home_here()
{
    if $have_sym_orig; then
        git checkout -q -B $sym_orig HEAD || exit
    fi
}

# Set ORIG_HEAD to whatever HEAD had at the start of the whole
# thing.
set_orig_head()
{
    git update-ref -m "squashbase" ORIG_HEAD $orig
}

# We have some series of commits:
#
#            A--B--C   <-- topic (HEAD)
#           /
#  ...--o--*--o--o--o   <-- upstream
#
# We must locate commit `*`, i.e., the commit that is the
# parent of commit A.  Commit A must be an ordinary commit,
# not a merge commit.  Also, we must forbid a case like this:
#
#              A-_
#             /   \
#            /  B--C
#           /  /
#  ...--o--*--*--o--o   <-- upstream
#
# But we must *allow* this case:
#
#           A--B--C---M   <-- topic (HEAD)
#          /         /
#  ...--o--*--o--o--*   <-- upstream
#
# where the current commit is a merge with the upstream, and one
# of its parents *is* the upstream.
#
# I've attempted to do this with --boundary though I am not sure
# this catches every case.  It does work for simple cases, though.

set -- $(git rev-list --boundary $upstream..HEAD |
    sed -n -e "/^-$upstream$/d" -e 's/^-//p')
case $# in
0) die "no boundary commits with $upstream_orig";;
1) ;;
*) die "multiple boundary commits with $upstream_orig";;
esac

# Detach HEAD now before we start fussing, so that branch name $sym_orig
# does not get extra reflog updates.  And, if something goes wrong after
# this point, use quit_early to attempt to put everything back.
quit_early()
{
    go_home
    exit 1
}
git checkout -q --detach || exit

# XXX this signal trapping is ugly and not well tested
trap : 1 2 3 15
if $interactive; then
    git rebase -i $1 || quit_early
    # At this point we have whatever commits the user left
    # us.  We will assume that these are the "right" commits
    # to rebase now.
else
    git checkout -q --detach $1 || quit_early
    # This merge *must* work, but --squash implies -n ...
    git merge --squash $orig || quit_early
    # ... so commit it now, with --edit
    git commit --edit || quit_early
    # At this point we have a single commit (from git merge --squash).
    # This is the "right" commit to rebase now.
fi

# We always use a non-interactive rebase here.  Note that this
# step may fail, or stop in the middle, so we must first re-attach
# HEAD as appropriate.  This is a bit unfortunate as we'll get one
# extra reflog entry, but there's no way around that.
set_home_here
git rebase --onto $target $upstream || {
    status=$?
    echo "You will have to finish the rebase yourself now."
    set_orig_head
    exit $status
}

# The rebase worked.  We already re-attached HEAD so the only thing
# we really want to do now is override the ORIG_HEAD that rebase set:
# we want the one from before the squashbase operation.
set_orig_head
exit 0

推荐阅读