问题复现与分析

场景描述

在 test-git 仓库内进行问题复现。该库目前仅含有 main.py 文件。初始代码如下(省略无关内容):

def print_hi(name):
    print(f'Hi, {name}')  # Press Ctrl+F8 to toggle the breakpoint.

if __name__ == '__main__':
    print_hi('PyCharm')

# second commit on master xx
# Some further commit on master 1.0.10
  1. 从 master 分支新建分支:feat-dev-7
  2. 在 master 继续提交,最后行为 # Some further commit on master 1.0.11
  3. 切换至 feat-dev-7,提交内容为 # second commit on master xx 1.0.11 patch,如下:
# ...省略主代码...
# second commit on master xx  1.0.11 patch
# Some further commit on master 1.0.10
  1. 在 PyCharm 上执行 “Rebase feat-dev-7 onto origin/master”。
  2. 执行 Push,此时出现 Push Rejected 警告:

Push of the current branch "feat-dev-7" was rejected. Remote changes need to be merged before pushing.

提供操作选项:Merge、Rebase、Cancel。


问题一:为何 Push 被拒绝?Merge 与 Rebase 有何区别?

1. Push Rejected 原因分析

出现此提示,是因为:

  • 在本地 feat-dev-7 分支上执行了 rebase,修改了提交历史(即本地提交的 hash 已改变)。
  • 远程的 feat-dev-7 依然保留了曾 push 的旧提交。此时,本地分支与远程分支历史不兼容,不能普通 push,除非采用 --force 强推覆盖远程历史。
  • 为了防止丢失他人提交,Git 默认阻止这样直接 push(被视作破坏性操作)。

2. Merge 与 Rebase 区别

操作 作用 是否改写历史 适用场景/风险
Merge 合并远程分支变更,生成合并提交 不会改写已有提交 推荐合作场景(保持历史不变)
Rebase 将本地提交重放在远程分支之上 会改写 commit hash 推送前本地整理历史,无多人协作更安全
强推 强制覆盖远程历史(--force 改写远程历史 仅一人维护分支时可用,注意风险

3. 典型正确做法

  • 如果在本地 rebase 后还未 push,可以直接 push --force。
  • 若团队多人才在共同开发此分支,不要 rebase 已 push 的分支历史,否则其他协作者很可能出现同步冲突。
  • 出现 push rejected 时,如果想保留远程同事历史,用 merge 更稳妥。如果想维护精简历史并确认只有自己操作,可用 rebase+force-push。

问题二:“Rebasing Published Commit” 警告含义

Rebasing Published Commit 警告是指:“正在 rebase 曾经已经 push(‘published’)到远端的提交”。一旦重写历史:

  • 同样内容的代码会生成 新的 commit hash
  • 如果他人基于旧的提交继续开发,将产生命名空间分叉和 “duplicate commits” 问题(内容重复但 hash 不同的提交)。
  • 此时如果选择 rebase 合并,必须用 git push --force,否则无法 push。

为什么会产生重复提交

这是由于 rebase 本质是“撤销旧提交,在新基础上生成新 commit”。远程的旧提交依然存在,如果直接拉取(pull 或 fetch-merge),会获得一条分叉的提交链,形成“同内容不同 hash” 的重复提交。

示意图:

# Rebase前 
A---B---C---D---G (master)
		\ 
		  E---F (feature) 
		  
# Rebase后 
A---B---C---D---G (master) 
				\ 
				  E'---F' (feature)
  • E’和E内容可以一样,但 hash 不同。如果此时 pull,会得到 E 和 E' 各自分叉的历史,混乱且臃肿。
A---B---C---D---G 
		|       \
		|        E'---F' (新)
		 \ 
		  E---F (旧)

最佳实践

  1. 未 push 的分支随便 rebase,整理历史后再 push。
  2. 已 push 并有协作的分支尽量不 rebase,宁可 merge,确保团队历史一致。
  3. 必须 rebase 已发布分支时:
    • 明确须 push --force,并告知合作者不要基于旧分支开发;
    • 确认你是唯一贡献者。
  4. 遇到 “duplicate commits” 或“分叉历史”时:
    • 建议通过查看 git reflog,再 git reset --hard 到 rebase 前的提交,然后用 git push --force 修正远程历史;
    • 或手动 cherry-pick 至新分支,丢弃重复历史。

查看分支差异的常用命令:

git fetch origin
git log --oneline --graph --all
git log origin/feat-dev-7..feat-dev-7

结语

  • 要合并远程变更,优先用 merge,保证协同稳定;
  • 需整理提交历史,优先本地 rebase(未 push 前),已 push 分支请慎用 rebase;
  • 一旦遇到历史纷乱(重复提交等),可通过 reset、reflog、cherry-pick 等手法纠正。