1 问题描述

如果多人同时开发同一个分支,使用哪种方案:

  • git pull = git fetch + git merge
  • git pull –rebase = git fetch + git rebase

2 结论

建议多人协同  直接merge,解决冲突简单,减低协同成本,尤其经常性合并master到最新分支。  多人协同开发同一个分支,使用rebase的话,协作成本高:
  • 涉及到合并master的 效率低,协同难
  • 解决冲突,可能需要多次,繁琐。
rebase价值?
  • 线性化:公司很多业务都是主干使用merge方式,主干都是非线性的,所以分支用rebase保证线性 也没啥意义
  • 规整commit:如果本地分支缩减commit,这个到是有价值。
关键结论:团队必须统一选择一种策略。
团队需求
推荐策略
配置命令
保持线性历史,追求简洁
统一用 git pull –rebase
  1. 配置全局默认行为(所有成员必须执行):
git config –global pull.rebase true # 所有git pull默认用–rebase
或针对当前仓库配置:
git config pull.rebase true
简化冲突解决,接受合并提交
如果master非线性的,开发分支更没必要线性化了)
  • git pull(merge):一次性解决所有冲突,生成合并提交。
  • git pull --rebase:需在变基过程中逐个提交解决冲突。
统一用 git pull (merge)
统一全团队使用 git pull(禁用rebase),接受合并提交的缺点:
git config –global pull.rebase false # 显式关闭rebase

3 多人 开发场景

  • git pull --rebase 的本质是:先将本地未推送的提交「暂时移除」,拉取远程最新代码后,再将本地提交「重新应用」到最新远程分支上。若有人已将 master 合并到 fa,其他开发者拉取时会遇到两种情况:
    • 无冲突:若本地修改与 master 合并的代码无重叠,Git 会自动按顺序应用本地提交,无需手动干预;
    • 有冲突:若本地修改与 master 合并的代码冲突,Git 会暂停变基(rebase),要求开发者手动解决冲突,解决后需使用 git rebase --continue 继续,直至所有本地提交都应用完毕。
  • 与普通合并的区别:
    • 普通合并(git pull)会生成一个「合并提交」,将本地与远程分支历史合并,冲突仅需解决一次;
    • 变基(git pull --rebase)会将本地提交「重新播放」在远程分支上,每个本地提交都可能触发冲突(若该提交修改的代码与 master 合并的代码有重叠)。因此,若本地有多个提交,可能需要多次解决冲突。

3.1 多个人开发 feature(基于master拉的分支)。如果有人将最新master 合并到 feature,对其他人有什么影响呢

Merge master 做了什么?执行 git checkout feature 后,再执行 git merge main呢

假设初始提交历史如下:

执行 git checkout feature 后,再执行 git merge main,Git 会:
  1. 找到共同祖先:确定 feature 和 main 的共同祖先(这里是 B)。
  2. 准备合并:将 main 分支(C)和当前分支(feature 的 E)的修改与共同祖先(B)进行对比。
  3. 创建合并提交:如果没有冲突,Git 会创建一个新的提交(M),该提交有两个父节点(E 和 C)。
  4. 更新分支指针:将 feature 分支指针指向新的合并提交(M)。
最终结果:

 

团队协作同步成本更高,需严格遵循流程

协作的开发开发的成本太高。
  • 信息同步要求: 若有人计划将 master 合并到 fa,必须提前通知团队,确保其他开发者暂停提交并同步远程分支。否则,若其他人正在基于旧的 fa 分支开发并执行变基,可能导致与 master 合并的代码产生复杂冲突。
  • 推荐协作流程:
    • 执行合并前:
      1. 合并者通知团队暂停提交;
      2. 所有人执行 git pull --rebase 同步最新 fa(此时可能需解决少量冲突);
      3. 合并者将 master 合并到 fa 并推送。
    • 合并后:
      1. 其他开发者执行 git pull --rebase,Git 会自动将本地提交「移到」master 合并提交之后,可能需解决冲突。

3.2 多个人开发 feature(基于master拉的分支)。如果有人将feature分支 rebase 到最新的master,对齐人有什么影响呢

当多人基于 master 分支拉取 feature 分支进行协作开发时,如果其中一个人将 feature 分支 rebase 到最新的 master,会对其他开发者产生以下影响:

Rebase master做了什么?执行 git checkout feature 后,再执行 git rebase main

当你执行 git rebase main 时,Git 会将 feature 分支的提交 “复制” 到 main 分支的最新提交之后,形成线性的提交历史。这个过程中,Git 实际上是丢弃了 feature 原有的提交记录,并在 main 的基础上重新应用这些提交的修改,生成新的提交对象。
假设初始提交历史如下:

执行 git checkout feature 后,再执行 git rebase main,Git 会:
  1. 找到共同祖先:确定 feature 和 main 的共同祖先(这里是 B)。
  2. 提取提交补丁:将 feature 分支上的提交(D、E)生成补丁文件。
  3. 切换到 main:临时切换到 main 分支的最新提交(C)。
  4. 应用补丁:依次将 D、E 的补丁应用到 main 上,生成新的提交 D’ 和 E’。
  5. 更新分支指针:将 feature 分支指针指向新的提交链。
最终结果:

 

核心问题:提交历史被重写

Rebase 会丢弃原有提交并生成新的提交对象,即使修改内容相同,提交 ID 也会改变。这导致:
  • rebase 者的本地历史与远程仓库一致。
  • 其他开发者的本地历史与远程仓库出现分叉,因为他们的提交 ID 仍然指向旧的提交。

具体影响场景

假设初始提交历史如下:
开发者 Alice 和 Bob 各自拉取了 feature 分支进行开发:
Alice 将 rebase 后的分支推送到远程:
此时,Alice 执行了git rebase master,将 feature 分支的提交重新应用到 master 最新提交 C 之后:
Alice 将 rebase 后的分支推送到远程:
远程仓库变为:

Bob 的困境

当 Bob 尝试推送自己的提交 G 时:

Bob 需要先拉取远程更新:
Git 会发现本地提交 E 与远程提交 E’ 的 ID 不同,从而提示非快进(non-fast-forward)冲突,并生成类似以下的提交历史:

Bob 如何解决冲突?

Bob 有两种选择,但都会导致混乱:

方案 1:强制推送(不推荐)

这会引发 “提交战争”,双方不断强制覆盖对方的提交。

方案 2:合并或 rebase(仍有问题)

Bob 可以尝试合并远程分支:
或再次 rebase:
无论哪种方式,都会导致提交历史变得混乱,难以维护。

更严重的问题:重复冲突

如果 Bob 和 Alice 继续在 feature 分支上开发,每次同步都会遇到重复冲突,因为 Git 无法识别 D/E 与 D’/E’ 是相同的修改。

如何避免这种情况?

  1. 禁止对共享分支 rebase:团队约定只对未推送的本地提交使用 rebase。
  2. 使用 merge 代替 rebase:多人协作时,优先使用git merge整合代码,保留完整历史。
  3. 保护远程分支:通过 Git 服务器配置(如 GitHub 的分支保护规则)禁止直接推送 feature 分支,所有修改必须通过 PR 合并。
  4. 定期同步 master:在开发早期频繁合并 master,减少冲突范围。

总结

对已共享的 feature 分支执行 rebase 会导致:
  1. 提交历史被重写,与其他开发者的本地历史分叉。
  2. 强制推送覆盖远程分支,可能丢失他人修改。
  3. 后续协作中频繁出现冲突,难以解决。
最佳实践:在多人协作的 feature 分支上,永远不要执行 rebase,改用git merge保持历史一致性。

3.3 混用的问题:有的时候使用git pull,有的时候使用git pull –rebase

在同一个分支上混合使用 git pull(默认合并方式)和 git pull --rebase 会导致严重问题,主要体现为历史记录混乱和冲突解决冗余。以下是具体分析和解决方案:

历史记录污染

  • git pull = git fetch + git merge 会产生多余的合并提交(如 Merge branch 'aa' into aa),污染分支历史。
  • git pull --rebase = git fetch + git rebase 保持线性历史,无合并提交。
  • 混合结果:历史记录中交替出现线性提交和合并提交,可读性极差:

混合问题举例

你的本地分支和远程分支出现了分叉(divergent branches),通常是由于团队成员混合使用了 git pull(合并方式)和 git pull --rebase(变基方式)导致的
  1. 分叉分支:你的本地提交和远程提交不在同一条历史线上

解决方案

4 Git rebase 介绍

两大作用:
  • 保证一个分支线性。Git rebase 实际上是丢弃了 feature 原有的提交记录,并在 main 的基础上重新应用这些提交的修改,生成新的提交对象,所以整个分支就是一条线,没有旁路分支,旁路都被删除了。rebase模式下,都是把旁路的comit丢弃,然后在分支最新header上回溯生成新的commit .
  • 聚合本地分支的多个commit为一个
开发流程
3解决冲突

3 Git介绍

Git 是一个分布式版本控制系统,核心原理围绕文件版本管理和分布式协作展开,以下是其核心原理的关键内容:
  1. 核心概念:快照而非差异
  • Git 不记录文件的 “变化差异”,而是保存文件在特定时刻的完整快照。
  • 当文件未修改时,Git 不会重复存储,而是通过指针指向之前的快照,节省空间。
  1. 三个核心区域
  • 工作区(Working Directory):本地可见的文件目录,正在编辑的文件存于此。
  • 暂存区(Staging Area):临时存放待提交的文件快照,可理解为 “提交前的缓冲区”,通过 git add 命令将工作区文件添加到暂存区。
  • 本地仓库(Local Repository):存储所有提交的快照和版本历史,通过 git commit 命令将暂存区内容提交到本地仓库。
  1. 分布式架构
  • 每个用户的本地仓库都是完整的,包含全部历史记录,无需依赖中央服务器即可独立工作。
  • 中央仓库(如 GitHub)主要用于同步协作,通过 git push(本地推送到远程)和 git pull(远程拉取到本地)实现多人代码同步。
  1. 版本标识:哈希值(SHA-1)
  • 所有快照、提交记录等都通过哈希值唯一标识(如 a1b2c3...),确保数据完整性(任何修改都会导致哈希值变化)。
  1. 分支管理
  • 分支本质是指向某个提交快照的可移动指针,默认主分支为 mainmaster
  • 新建分支(git branch <name>)会创建一个新指针,切换分支(git checkoutgit switch)则移动当前指针指向目标分支。
  • 合并分支(git merge)可将不同分支的修改整合,解决冲突后形成新的提交。

Git commit对象解析

在 Git 中,commit 对象是版本控制系统的核心组件之一,它记录了代码库在某个时间点的状态。理解 commit 对象的结构和原理,有助于掌握 Git 的底层机制。
  1. Commit 对象的核心作用
  • 保存代码快照:每个 commit 代表项目在某个时刻的完整状态(通过树对象 Tree 引用所有文件)。
  • 记录历史关系:通过 parent 指针连接到父提交,形成提交链(即版本历史)。
  • 元数据存储:包含作者、提交者、时间戳、提交信息等元数据。
  1. Commit 对象的组成结构
每个 commit 对象由以下部分构成:

(1)元数据(Metadata)

  • 作者(Author):编写代码的人(格式:姓名 <邮箱> 时间戳)。
  • 提交者(Committer):执行 git commit 的人(通常与作者相同,但若代码被他人提交,两者会不同)。
  • 提交时间(Timestamp):记录提交的时间点。
  • 提交信息(Message):描述本次提交的目的(如 "Fix bug #123")。

(2)内容引用

  • 树对象(Tree):指向当前提交的文件目录结构(即项目快照)。
  • 父提交(Parent):指向该提交的上一个提交(第一次提交无父节点,合并提交可能有多个父节点)。

(3)哈希值(SHA-1)

  • 每个 commit 对象通过内容计算生成唯一的 40 位哈希值(如 a1b2c3d4...),用于标识和验证提交。
  1. Commit 对象的底层存储
Git 使用 对象存储(Object Store) 来保存 commit 对象,这些对象位于仓库的 .git/objects 目录中。每个 commit 对象实际上是一个经过压缩的文件,包含上述所有信息。
例如,一个简化的 commit 对象内容可能如下:

  1. Commit 对象与其他 Git 对象的关系
Git 中有四种核心对象:
  • Blob:存储文件内容。
  • Tree:表示目录结构,包含 Blob 和子 Tree。
  • Commit:指向一个 Tree,并记录元数据。
  • Tag:指向特定 Commit 的引用(如版本标签)。
它们的关系如下:
  • 每个 commit 指向一个根 Tree 对象。
  • Tree 对象递归引用子目录和文件(Blob)。
  • Blob 对象存储实际的文件内容。
  1. 提交链与分支
  • 提交链:通过 parent 指针,多个 commit 对象形成一条线性历史。
  • 分支:本质是指向某个 commit 的指针(如 main 分支指向最新提交)。
  • HEAD:指向当前活动分支的指针。
例如,一个简单的提交历史可能是:

  1. 如何查看 Commit 对象详情?
  • 查看提交信息:git show <commit-hash>
  • 查看提交树结构:git ls-tree <commit-hash>
  • 查看原始对象内容:git cat-file -p <commit-hash>

总结

Commit 对象是 Git 的核心数据结构,它通过引用树对象(Tree)保存文件快照,通过父指针(Parent)连接历史,形成完整的版本控制系统。理解这一结构有助于深入掌握 Git 的工作原理,尤其是在处理复杂的历史操作(如变基、合并)时。

Git 分叉指什么

在 Git 中,分叉(Diverged) 指的是本地分支与远程分支的提交历史出现分歧的状态。当两个分支基于同一提交点分别发展出不同的提交时,就会形成 “分叉”。

分叉的本质

Git 的分支是指向提交对象的指针。当多人协作时,如果:
  • 用户 A 在本地分支 feature 上提交了新内容,并推送到远程。
  • 用户 B 也在本地 feature 分支开发,但未同步远程最新提交,直接在本地提交了新内容。
此时,本地和远程的 feature 分支会各自发展出不同的提交历史,形成分叉。

典型场景:多人协作同一分支

  1. 初始状态:本地和远程 feature 分支都指向提交 C3

 

  1. 用户 A 在本地提交 C4 并推送到远程:

  1. 用户 B 在未拉取远程更新的情况下,本地提交 C5

 

  1. 分叉形成:
    1. 远程分支 origin/featureC1 → C2 → C3 → C4
    2. 本地分支 featureC1 → C2 → C3 → C5
此时,Git 会提示 “本地分支与远程分支分叉”(Your branch and origin/feature have diverged)。
5、分叉的影响
  • (1)无法直接推送:若用户 B 尝试 git push,Git 会拒绝,因为提交历史与远程不一致。
    (2)需合并或变基:解决分叉需通过以下方式同步历史:
  • 合并(Merge):使用 git pull 将远程分支合并到本地,生成一个新的 “合并提交”(如 C6)。

    • 变基(Rebase):使用 git pull --rebase 将本地提交 “移动” 到远程分支的最新提交之后,保持线性历史。
    1. 避免分叉的最佳实践
    • 频繁拉取更新:每次开发前先 git pull --rebase,保持本地与远程同步。
    • 分支隔离:避免多人直接在同一分支开发,使用独立分支(如 feature/userB)开发后再合并。
    • 理解工作流:根据团队协作模式选择合适的工作流(如 Git Flow、GitHub Flow)。

    总结

    分叉是多人协作中常见的情况,本质是提交历史的分歧。通过合理的拉取、合并或变基操作,可以解决分叉并保持代码历史的一致性。

分类&标签

Category : Git