外观
同步一个Fork
- 给 fork 配置一个 remote
#使用 `git remote -v` 查看仓库状态
git remote -v
# origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch)
# origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (push)
# 添加上游仓库地址
git remote add upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git
# 再次查看状态确认是否配置成功。
git remote -v
# origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch)
# origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (push)
# upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git (fetch)
# upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git (push)- 拉取上游仓库 fetch 分支和提交点,传送到本地,并会被存储在一个本地分支 upstream/master
git fetch upstream
git fetch upstream <分支名> # 拉取指定分支
# remote: Counting objects: 75, done.
# remote: Compressing objects: 100% (53/53), done.
# remote: Total 62 (delta 27), reused 44 (delta 9)
# Unpacking objects: 100% (62/62), done.
# From https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY
# * [new branch] master -> upstream/master- 把 upstream/master 分支合并到本地 master 上,这样就完成了同步,并且不会丢掉本地修改的内容。有冲突的话合并一下
git merge --squash upstream/master # 在主分支时合并,不合并上游分支的提交历史
# Updating a422352..5fdff0f
# Fast-forward
# README | 9 -------
# README.md | 7 ++++++
# 2 files changed, 7 insertions(+), 9 deletions(-)
# delete mode 100644 README
# create mode 100644 README.md
# 有可能会出现fatal: refusing to merge unrelated histories的问题
git merge forkmerge
# 参数:
--allow-unrelated # 解决fatal: refusing to merge unrelated histories,分支已经分叉并且没有共同的提交或历史记录
--squash # 不保留commit历史
#简单点的 无冲突的情况下合并之后直接push 不commit不会产生合并记录
git merge upstream/master
git push- 如果想更新到 GitHub 的 fork 上,直接
git push origin master就好了。
说明
1. 不会产生新提交记录的情况(Fast-Forward 合并)
git checkout master
git fetch upstream/v2
git merge upstream/v2
git push --set-upstream origin master前提:如果你的本地 main 分支和 upstream/main 分支之间没有分叉(即你的本地 main 分支的提交历史完全是 upstream/main 的子集),那么 git merge upstream/main 会执行 Fast-Forward 合并。
结果:
- 本地 main 分支的 HEAD 指针会直接移动到 upstream/main 的最新提交。
- 不会生成新的合并提交(Merge Commit)。
- 推送
git push origin main时,origin/main 只是更新到与 upstream/main 相同的提交,没有额外的提交记录。
提交历史示例:
A --- B --- C (upstream/main, origin/main, local main)在这种情况下,历史保持线性,没有新提交。
2. 会产生新提交记录的情况(Merge Commit)
git checkout master
git fetch upstream/v2
git merge upstream/v2
git commit -m '有不同分支的时候,会被创建新的提交记录'
git push --set-upstream origin master前提:如果你的本地 main 分支上有自己的提交(例如你在 main 上修改并提交了内容),而这些提交不在 upstream/main 中,那么 git merge upstream/main 会创建一个 合并提交(Merge Commit)。
结果:
- Git 会将 upstream/main 的更改合并到本地 main,生成一个新的合并提交。
- 这个新提交会包含两个父提交:一个指向本地 main 的最新提交,一个指向 upstream/main 的最新提交。
- 推送 git push origin main 时,这个合并提交会被推送到 origin/main,从而产生新的提交记录。
提交历史示例:
A --- B (upstream/main) \ C --- D (local main 前状态) \ M (local main 合并后,M 是合并提交)这里 M 是新的合并提交。
3. 如何判断是否会产生新提交?
在执行 git merge upstream/main 之前,可以通过以下方式预判:
- 查看分支状态:
git fetch upstream git log main..upstream/main- 如果输出为空,说明 main 已包含 upstream/main 的所有提交,合并将是 Fast-Forward。
- 如果有输出,说明 upstream/main 有新提交需要合并。
新方案
会!这是最常见的问题。当你用 --orphan 创建干净分支后,你的分支和上游分支没有共同祖先,Git 会拒绝合并。
为什么会出现
你的历史: A (clean-main, 你的第一个commit)
上游历史: o---o---o---...---Z (upstream/main, 几万个commit)
↑
完全不同的起点,无共同祖先Git 默认拒绝合并没有关联的历史,防止意外合并错误的内容。
解决方案
方案 1:强制允许无关历史(推荐,最简单)
git fetch upstream
# 关键:添加 --allow-unrelated-histories
git merge upstream/main --squash --allow-unrelated-histories -m "Sync: Upstream update"Squash + --allow-unrelated-histories 的组合效果:
- ✅ 允许合并没有共同祖先的历史
- ✅ 但结果仍然是 1 个干净的 commit(因为
--squash不保留上游的提交历史) - ✅ 下次同步时,你的 main 和 upstream/main 仍然没有共同祖先,需要继续用
--allow-unrelated-histories
方案 2:建立"假"共同祖先(更优雅,长期更好)
如果你不想每次都用 --allow-unrelated-histories,可以人为制造一个连接点:
# 1. 先创建你的干净分支
git checkout --orphan clean-main
git add -A
git commit -m "Initial: Based on upstream v1.0"
# 2. 获取上游的第一次 commit(作为"假祖先")
git fetch upstream
# 找到上游的第一个 commit
FIRST_COMMIT=$(git rev-list --max-parents=0 upstream/main)
echo "上游第一个 commit: $FIRST_COMMIT"
# 3. 变基到上游第一个 commit 上(制造关联)
git rebase --onto $FIRST_COMMIT --root
# 现在你的第一个 commit 的 parent 是上游的第一个 commit
# 历史看起来: 上游第一个 commit → 你的干净 commit效果:
历史现在: [上游第一个commit] --- A (你的干净commit)
↑
也是 upstream/main 的祖先之一这样之后 git merge upstream/main 就不需要 --allow-unrelated-histories 了。
方案 3:使用 git replace 或 git commit-tree(高级)
完全伪造一个合并关系,但过于复杂,一般不推荐。
推荐的工作流(整合解决方案)
如果你选择方案 1(每次用 --allow-unrelated-histories)
#!/bin/bash
# sync-upstream.sh
set -e
cd /path/to/your/repo
echo ">>> Fetching upstream..."
git fetch upstream
echo ">>> Squash merging upstream changes..."
# 必须加 --allow-unrelated-histories
git merge upstream/main --squash --allow-unrelated-histories \
-m "Sync: Merge upstream changes ($(date +%Y-%m-%d))"
echo ">>> Done. Check status and commit:"
git status特点:
- 简单直接,每次同步都是 1 个命令
- 需要记住加
--allow-unrelated-histories - 你的历史永远只有你自己的 commits + 定期的 squash 合并 commits
如果你选择方案 2(建立假祖先,推荐长期使用)
# ===== 初始化(只做一次)=====
git clone https://github.com/YOUR_USERNAME/repo.git
cd repo
git remote add upstream https://github.com/ORIGINAL/repo.git
# 获取上游完整历史
git fetch upstream
# 找到上游第一个 commit
FIRST=$(git rev-list --max-parents=0 upstream/main)
# 创建 orphan 分支
git checkout --orphan temp-clean
# 添加你的初始代码
git add -A
git commit -m "Initial: Clean base from upstream"
# 变基到上游第一个 commit 上,建立关联
git rebase --onto $FIRST --root
# 强制推送到 main
git branch -D main
git branch -m main
git push -f origin main
# ===== 以后同步(不需要 --allow-unrelated-histories 了)=====
git fetch upstream
git merge upstream/main --squash -m "Sync: Upstream update $(date +%Y-%m-%d)"常见问题排查
Q: 用了 --allow-unrelated-histories 但还是报错?
# 检查是否真的 fetch 了上游
git remote -v
git branch -r # 应该能看到 upstream/main
# 如果没有,重新添加 remote
git remote remove upstream
git remote add upstream <正确的URL>
git fetch upstreamQ: Squash 合并后,下次同步有冲突?
这是正常的。因为每次 squash 合并后,你的 main 和 upstream/main 的内容虽然相同,但提交历史不同。
# 解决冲突的标准流程
git merge upstream/main --squash --allow-unrelated-histories
# 会有冲突提示,解决后
git add -A
git commit -m "Sync: Upstream update with conflict resolution"Q: 想要更激进的方案 - 完全替换历史但保留同步?
# 使用 git worktree 双轨制
# 你的干净仓库:只放你的代码
# 另一个目录:完整上游仓库,用于查看和 cherry-pick
# 创建上游工作区
git worktree add ../upstream-work upstream/main
# 需要某个上游功能时,手动 cherry-pick 或复制文件
cd ../upstream-work
git log --oneline -20 # 找想要的 commit
cd ../your-repo
git cherry-pick <commit-hash> # 或手动合并文件最终建议
| 场景 | 推荐方案 |
|---|---|
| 快速开始,不介意每次打长命令 | 方案 1 --allow-unrelated-histories |
| 长期维护,想要标准 Git 工作流 | 方案 2 建立假祖先 |
| 极少同步上游,主要独立开发 | 双工作区手动 cherry-pick |
最实用的快速命令:
# 初始化
git checkout --orphan clean-main && git add -A && git commit -m "init" && git push -f origin clean-main:main
# 每次同步(记住这个就行)
git fetch upstream && git merge upstream/main --squash --allow-unrelated-histories -m "sync: $(date +%Y-%m-%d)"