奇怪的 git 合并问题

发布于 2024-08-21 20:38:18 字数 362 浏览 4 评论 0原文

这是 gitk 目前在我们的一个项目中的样子:

https://dl.dropbox。 com/u/2582508/gitk.png

据我们所知,这显然是在远程分支完成一次“git merge”之后发生的 - 我们不确定为什么或发生了什么。知道这里发生了什么吗?

更重要的是,修复它的最佳方法是什么?这些合并提交是空的,但是当执行“git rebase -i”时,合并提交通常似乎不会出现。

最重要的是,我不想让历史与其他克隆不兼容,即它们仍然应该能够与其拉/推/合并。这可能吗?

This is how gitk currently looks in one of our projects:

https://dl.dropbox.com/u/2582508/gitk.png

This apparently, from all we can tell, happened after a single "git merge" was done with a remote branch - we're not sure why or what is going on. Any idea what happened here?

More importantly, what's the best way to fix it? Those merge commits are empty, but when doing "git rebase -i" merge commits generally don't seem to appear.

Most importantly, I would prefer to not make the history incompatible with other clones, i.e. they should still able to pull/push/merge with it. Is that even possible?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

弃爱 2024-08-28 20:38:18

这是某人使用 git pull(或者等效地,git fetch 和 git merge)更新其项目的结果。想象一下,您的存储库看起来像这样:

o---o---o [origin/master]
         \
          A---B---C [master]

也就是说,您已经在原始仓库。

与此同时,其他人进行更改并将其推送到共享存储库。如果你然后运行 ​​git fetch ,你的存储库看起来像这样:

o---o---o---D---E---F [origin/master]
         \
          A---B---C [master]

现在,如果你运行 git merge (记住:git pull 只是 git fetch 后跟 git merge)。你会得到这样的结果:

o---o---o---D---E---F [origin/master]
         \           \
          A---B---C---G [master]

假设一切顺利,G可能只是一个“愚蠢的”合并提交;从技术上讲,G 的状态与 FC 不同,因此必须以不同方式考虑。

现在,如果你推动这个改变,你会得到这个:

o---o---o---D---E---F 
         \           \
          A---B---C---G [origin/master]

如果你继续开发,你会得到这个:

o---o---o---D---E---F 
         \           \
          A---B---C---G [origin/master]
                       \
                        H---I---J [master]

现在,如果你继续这样做(并且如果很多人继续这样做),你最终会得到一棵像这样的树你照片中的那个。这并不是“错误”,但很多人不喜欢它,因为它使得发展历史很难遵循。

这个问题的解决办法就是教人们rebase。变基将(正如您所指出的)远程无用的合并提交,并为您提供更清晰的历史记录。在上面的例子中,你最终会得到一个线性的开发历史。这更容易遵循。但是,您需要知道,在变基之后,您需要重建并重新测试您的代码……仅仅因为代码很容易合并并不意味着结果是正确的。

如果您使用中央存储库来共享官方开发线,您可以实现一个预接收钩子来检测这些自动(“愚蠢”/“无用”)合并提交并拒绝推送 - 强制用户重新设置基准。实际上,您希望挂钩查找默认的合并提交消息...因此,如果您确实想保留合并提交(有时这是有意义的),您至少必须编写一条智能提交消息。

这是一个钩子示例。我去掉了一些额外的东西,所以我没有测试它。

#!/bin/bash

# This script is based on Gnome's pre-receive-check-policy hook.
# This script *only* checks for extraneous merge commits.

# Used in some of the messages
server=git.wherever.com

GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)

in_import() {
    test -e "$GIT_DIR/pending"
}

forced() {
    test -n "$GNOME_GIT_FORCE"
}

check_commit() {
    commit=$1

    subject="$(git log $commit -1 --pretty=format:%s)"
    if expr "$subject" : ".*Merge branch.*of.*\(git\|ssh\):" > /dev/null 2>&1; then
          if ! in_import && ! forced ; then
                cat &2
---
The commit:

EOF
        git log $commit -1 >&2
        cat &2

Looks like it was produced by typing 'git pull' without the --rebase
option when you had local changes. Running 'git  pull --rebase' now
will fix the problem. Then please try, 'git push' again. Please see:

  http://live.gnome.org/Git/Help/ExtraMergeCommits
---
EOF
        exit 1
          fi
    fi
}

check_ref_update() {
    oldrev=$1
    newrev=$2
    refname=$3

    change_type=update
    if expr $oldrev : "^0\+$" > /dev/null 2>&1; then
    change_type=create
    fi

    if expr $newrev : "^0\+$" > /dev/null 2>&1; then
          if [ x$change_type = xcreate ] ; then
             # Deleting an invalid ref, allow
                return 0
          fi
          change_type=delete
    fi

    case $refname in
     refs/heads/*)
        # Branch update
        branchname=${refname#refs/heads/}

        range=
        # For new commits introduced with this branch update, we want to
        # run some checks to catch common mistakes.
        #
        # Expression here is same as in post-receive-notify-cia; we take 
        # all the branches in the repo, as "^/ref/heads/branchname", other
       # than the branch we are actualy committing to, and exclude commits
       # already on those branches from the list of commits between
       # $oldrev and $newrev.

        if [ -n "$range" ] ; then
        for merged in $(git rev-parse --symbolic-full-name --not --branches | \
                    egrep -v "^\^$refname$" | \
            git rev-list --reverse --stdin "$range"); do
            check_commit $merged
        done
        fi
        ;;
    esac

    return 0
}

if [ $# = 3 ] ; then
    check_ref_update $@
else
    while read oldrev newrev refname; do
    check_ref_update $oldrev $newrev $refname
    done
fi

exit 0

This is the result of someone updating their project using git pull (or, equivalently, git fetch and git merge). Imagine you're repository looks like this:

o---o---o [origin/master]
         \
          A---B---C [master]

That is, you've done commits A, B and C on top of what was in the original repo.

In the mean time, some else makes changes and pushes them up to the shared repository. If you then run git fetch your repository looks like this:

o---o---o---D---E---F [origin/master]
         \
          A---B---C [master]

Now, if you run git merge (remember: git pull is just git fetch followed by git merge). You'll have this:

o---o---o---D---E---F [origin/master]
         \           \
          A---B---C---G [master]

Assuming all goes well, G is probably just a "stupid" merge commit; technically, the state of G is different from F and C and so it must be considered differently.

Now, if you push this change, you'll have this:

o---o---o---D---E---F 
         \           \
          A---B---C---G [origin/master]

And if you continue development you'll get this:

o---o---o---D---E---F 
         \           \
          A---B---C---G [origin/master]
                       \
                        H---I---J [master]

Now, if you keep doing this (and if many people keep doing this) you'll end up with a tree like the one in your picture. This isn't "wrong" but many people don't like it because it makes the development history very hard to follow.

The solution to this problem is to teach people to rebase. Rebasing will (as you noted) remote the useless merge commits and git you a much cleaner history. In the case above, you'll end up with a linear development history. That's much easier to follow. However, you need to know that after you rebase you need to rebuild and retest your code... just because the code merged easily doesn't mean the result is correct.

If you're using a central repository to share an official development line, you can implement a pre-receive hook that detects these automatic ("stupid"/"useless") merge commits and rejects the push -- forcing the user to either rebase. Actually, you want the hook to look for the default merge commit message... so, if you really do want to keep the merge commit (sometimes that makes sense) you must at least write an intelligent commit message.

Here's an example hook. I stripped a bunch of extra stuff out so I didn't get to test it.

#!/bin/bash

# This script is based on Gnome's pre-receive-check-policy hook.
# This script *only* checks for extraneous merge commits.

# Used in some of the messages
server=git.wherever.com

GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)

in_import() {
    test -e "$GIT_DIR/pending"
}

forced() {
    test -n "$GNOME_GIT_FORCE"
}

check_commit() {
    commit=$1

    subject="$(git log $commit -1 --pretty=format:%s)"
    if expr "$subject" : ".*Merge branch.*of.*\(git\|ssh\):" > /dev/null 2>&1; then
          if ! in_import && ! forced ; then
                cat &2
---
The commit:

EOF
        git log $commit -1 >&2
        cat &2

Looks like it was produced by typing 'git pull' without the --rebase
option when you had local changes. Running 'git  pull --rebase' now
will fix the problem. Then please try, 'git push' again. Please see:

  http://live.gnome.org/Git/Help/ExtraMergeCommits
---
EOF
        exit 1
          fi
    fi
}

check_ref_update() {
    oldrev=$1
    newrev=$2
    refname=$3

    change_type=update
    if expr $oldrev : "^0\+$" > /dev/null 2>&1; then
    change_type=create
    fi

    if expr $newrev : "^0\+$" > /dev/null 2>&1; then
          if [ x$change_type = xcreate ] ; then
             # Deleting an invalid ref, allow
                return 0
          fi
          change_type=delete
    fi

    case $refname in
     refs/heads/*)
        # Branch update
        branchname=${refname#refs/heads/}

        range=
        # For new commits introduced with this branch update, we want to
        # run some checks to catch common mistakes.
        #
        # Expression here is same as in post-receive-notify-cia; we take 
        # all the branches in the repo, as "^/ref/heads/branchname", other
       # than the branch we are actualy committing to, and exclude commits
       # already on those branches from the list of commits between
       # $oldrev and $newrev.

        if [ -n "$range" ] ; then
        for merged in $(git rev-parse --symbolic-full-name --not --branches | \
                    egrep -v "^\^$refname$" | \
            git rev-list --reverse --stdin "$range"); do
            check_commit $merged
        done
        fi
        ;;
    esac

    return 0
}

if [ $# = 3 ] ; then
    check_ref_update $@
else
    while read oldrev newrev refname; do
    check_ref_update $oldrev $newrev $refname
    done
fi

exit 0
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文