想象你是一位作家,正伏案创作一部长篇小说。桌面摊着十几张写满修改痕迹的草稿纸,墙角立着一个装满旧稿的档案柜。昨天你刚把第七章改了三遍,今天突然想找回初稿——可那页纸早被你揉成一团扔了。此刻你多想有一台“时光机”,让每一稿都留下印记,随时回到任意一个版本的进度。 Git 就是程序员世界的这台时光机。它管理的对象不叫小说,而叫“代码”——那些构成网站、App、乃至 AI 模型的每一行语句。每一次修改、每一个想法、每一条被推翻的设计,都能在 Git 的档案室里留下完整的“手稿”。想试验新方案?大胆去试,失败了一条命令退回昨天。写错了不可怕,可怕的是没有后悔药。 不过,Git 入门最大的障碍不是命令太多,而是“心智模型”没建立。你不需要先背五十条指令,只要理解三个文件夹(工作区、暂存区、版本库)如何交接,以及“分支”“合并”这两个让故事线平行展开又交汇的魔法,剩下的操作都会变得顺理成章。本文就用“作家书房”这个比喻,带你走完一遍 Git 的核心旅程。

岗位关联

掌握 Git 是 AI 工程师进入任何现代团队的入场券——从实验代码、数据管线到模型部署,没有版本控制,一切协作都会寸步难行。

目录
  1. 可视化演示
  2. 版本控制概念:代码也需要“档案柜”
  3. 初始化与配置:布置你的“作家书房”
  4. 暂存与提交:把灵感装订成册
  5. 分支管理:平行宇宙的故事线
  6. 合并操作:故事线的汇流
  7. 冲突解决:两位作者的“争执”
  8. 撤销操作:时光机与后悔药
  9. 协作基础:合著者的共享书房
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示Git从工作区编辑、暂存区add、版本库commit到分支merge的完整工作流,直观呈现三区数据流转与分支合并机制。

本节配有交互动效(进入视口后自动加载,约 3KB)。

工作区:文件编辑与修改 工作区是本地文件系统,git跟踪文件变更状态 git status查看工作区与暂存区的差异 三区模型是Git版本控制的核心心智模型

版本控制概念:代码也需要“档案柜”

没有版本控制的开发,像把全部草稿散在桌面上。今天改一段,明天删一段,等想找回昨天那段精彩的描写时,才发现早已无迹可寻。版本控制系统,就是这个“作家书房”里的档案柜——每一种状态都被完整保存,随取随用。Git 正是当前最流行的分布式版本控制系统,它的核心思想是快照而非差异:每一次提交,不是记录“改了几行”,而是把整个项目当时的模样拍成一张照片,连同编号和说明一起存入版本库。

要理解 Git,先记住最基本的心智模型:文件在三个区域之间流转。工作区是你正在编辑的文件实际所在的地方,对应作家书桌上的草稿纸;暂存区是你挑选后准备归档的稿纸集合,对应“收藏盒”;版本库是已经装订成册的手稿所在处,对应档案柜。工作区 → 暂存区 → 版本库,是一条单行道。

就这样,每一次灵感改动,都从草稿(工作区)被精心挑选进收藏盒(暂存区),最后装订成册放进档案柜(版本库)。想翻看上周的某份草稿?别怕,档案柜里按时间码放得整整齐齐。

工作区→git add暂存区→git commit版本库\text{工作区} \xrightarrow{\text{git add}} \text{暂存区} \xrightarrow{\text{git commit}} \text{版本库}
这个公式概括了 Git 最核心的“三区流转”:修改总要经过暂存,才能变成一次正式的提交。
  1. 工作区(Working Directory)你书桌上正在写的草稿纸,就是磁盘上看到的项目文件,所有修改都发生在这一层。
    工作区=项目文件夹中的实际文件\text{工作区} = \text{项目文件夹中的实际文件}
  2. git add 命令把选中的草稿纸放进收藏盒,让 Git 开始追踪这些文件的最新状态。
    →git add\xrightarrow{\text{git add}}
  3. 暂存区(Index / Staging Area)收藏盒里等待装订的稿纸,代表“下一次提交会包含什么内容”。
    暂存区=Index(索引)\text{暂存区} = \text{Index(索引)}
  4. git commit 命令把收藏盒里的稿纸装订成册放进档案柜,生成一个不可变的提交快照。
    →git commit\xrightarrow{\text{git commit}}
  5. 版本库(Repository)档案柜中按时间排列的册子,保存了项目的全部历史记录。
    版本库=所有提交的集合\text{版本库} = \text{所有提交的集合}

初始化与配置:布置你的“作家书房”

新作家搬进工作室,第一件事是什么?摆好书架、备好档案柜、在扉页上写好自己的名字。Git 的初始化和配置,就是给项目“开设书房”的过程。使用 Git 的第一步,是把一个普通文件夹变成 Git 仓库:执行 git init 后,项目目录下会出现一个隐藏的 .git 文件夹——它就是那座“档案柜”,里面存放全部历史信息。

初始化之后,你需要告诉 Git 你是谁:git config --global user.name 和 git config --global user.email。这就像在新册子的扉页上签上笔名和联系方式,每次提交都会自动盖上这个章。配置完成后,用 git status 随时检查书桌的状态——哪些稿纸是新的、哪些还没放进收藏盒、哪些已经归档完毕。

书房布置完毕:档案柜(.git)立好了,扉页签名(config)写好了,窗边还挂了一面“工作状态指示牌”(status)。现在,你可以安心开始创作第一行代码了。

Repository=init(project_dir)\text{Repository} = \text{init}(\text{project\_dir})
一个普通目录,经过 init 操作,就摇身一变成为可追溯历史的 Git 仓库。
  1. Repository(仓库)装订着全部手稿的档案柜,保存了所有历史记录和版本信息。
    Repository=Git 仓库地址\text{Repository} = \text{Git 仓库地址}
  2. init(初始化)创建一个空档案柜,即在项目目录中生成 .git 子目录。
    init⇒创建 .git 目录\text{init} \Rightarrow \text{创建 } .git \text{ 目录}
  3. project_dir(项目目录)你想纳入管理的文件夹,也就是“书房”本身。
    project_dir=代码项目根目录\text{project\_dir} = \text{代码项目根目录}

暂存与提交:把灵感装订成册

作家的手稿不是写一页就立刻装订的——他会先把满意的稿纸挑进收藏盒,攒够一册再装订。Git 的 add 和 commit,正是这个“挑选-装订”的节奏控制。git add 把工作区中改动过的文件放入暂存区;git commit 把暂存区的全部内容打包成一个提交快照,写入版本库。为什么要分两步?因为一次提交应当代表一个完整的、有意义的改动单元——先挑选,再归档。

提交之后用 git log 查看历史:每个提交都有唯一的哈希编号、作者、日期和提交说明。注意,提交的是“快照”而非“补丁”——版本库里存的是当时所有被追踪文件的完整复制,而不是一行行的差异。这也是为什么 Git 切换到任意历史版本都极其迅捷。

每一次 commit,就是给档案柜新增一册归档手稿。你可能会问:“那我到底存了多少册?”——git log 就是档案柜门口那本密密麻麻的登记簿。

Commit=Snapshot(Index,parent,author,message)\text{Commit} = \text{Snapshot}(\text{Index}, \text{parent}, \text{author}, \text{message})
一次提交的本质包含四个要素:暂存区内容、父提交、作者信息、提交说明。
  1. Snapshot(快照)把整个暂存区的状态拍成一张“照片”,完整复制所有被追踪文件。
    Snapshot=当前所有文件的完整复制\text{Snapshot} = \text{当前所有文件的完整复制}
  2. Index(暂存区)收藏盒里的稿件集合,即这次提交将要包含的全部文件。
    Index=暂存区内容\text{Index} = \text{暂存区内容}
  3. parent(父提交)这份册子的上一册编号,提交历史靠它串成一条链。
    parent=上一次提交的哈希值\text{parent} = \text{上一次提交的哈希值}
  4. author(作者)来自 git config 的签名,记录了作者的姓名与邮箱。
    author=(name,email)\text{author} = (\text{name}, \text{email})
  5. message(提交说明)册子封面的一句话摘要,解释这次提交做了什么。
    message=提交时附带的说明文本\text{message} = \text{提交时附带的说明文本}

分支管理:平行宇宙的故事线

写过小说的都知道,有时主线写到一半,突然冒出个“如果主角当初没离开小镇会怎样”的念头。Git 的分支,就是在稿纸堆里插一面小旗,开辟一条平行宇宙的故事线——你可以安心在支线上写疯狂的情节,主线纹丝不动。git branch 创建一个分支。在 Git 内部,分支其实只是一个“指针”,指向某个提交。git switch 或 git checkout 则负责把“当前正在哪条线上”切换过去。切换后,工作区的文件会跟着变成那条分支上的最新状态。

Git 还有一个特殊指针叫 HEAD,它指向当前所在的分支,相当于作家手指正按着的那条故事线。git switch -c new-branch 可以一步完成“创建并切换”。在分支上提交新内容,只会推动这个分支的指针,其他分支完全不受影响。想象一下:主线写到了第 10 章,你在 feature 分支上试验“平行宇宙”已经写到了第 3 章——两边互不打扰,各存各的册子。

现在,你的书房里可以同时摊开好几条故事线:主线在写常规剧情,feature 分支在实验“平行宇宙”,互不干扰,各有进度。等支线写好了,就可以考虑把它合回主线——这就进入了“合并”的环节。

Branch=指针→Committop\text{Branch} = \text{指针} \rightarrow \text{Commit}_{\text{top}}
分支不是文件夹的复制,而是一个指向最新提交的轻量指针,创建和切换都极其廉价。
  1. Branch(分支)平行故事线的“小旗”,标识一条独立的发展线。
    Branch=一个可移动的引用\text{Branch} = \text{一个可移动的引用}
  2. 指针(Pointer)指向某个提交对象的轻量引用,创建分支几乎零成本。
    指针↦Committop\text{指针} \mapsto \text{Commit}_{\text{top}}
  3. Commit_top(顶端提交)该分支上最新的提交,分支每前进一步都更新这个指针。
    Committop=分支最新提交\text{Commit}_{\text{top}} = \text{分支最新提交}

合并操作:故事线的汇流

支线剧情写得出彩,作家自然想把它并入主线。git merge 就是这样一位“编辑”,把两条故事线汇合成一本书。合并有两种常见情形。快进合并:主线自打分支以来没有任何新提交,那么直接把主线的指针向前推进到支线顶端即可,历史保持线性。三方合并:主线也有了自己的新提交,此时 Git 会取出“共同祖先”、当前分支顶端、目标分支顶端三份内容进行融合计算。

执行 git merge feature 时,Git 自动定位共同祖先。快进合并只是移动指针,不会产生新提交;三方合并则会生成一个“合并提交”,它有两位父提交——这份册子同时继承了两位作者的遗产。无论哪种方式,合并完成后,两条分支的成果就归于一统。在团队协作中,频繁的小步提交 + 及时合并,是减少合并痛苦的良方。

快进合并像是把支线稿纸直接插进主线册子,一气呵成;三方合并则像编辑对着三份底稿逐句比对。多数时候编辑能自动搞定,但若两边改了同一句话,争执就出现了——那就是下一节的“冲突”。

Merge=Combine(ancestor, maintop, featuretop)\text{Merge} = \text{Combine}\left(\text{ancestor},\ \text{main}_{\text{top}},\ \text{feature}_{\text{top}}\right)
三方合并的三个输入:共同祖先 + 两条分支各自的最新提交。
  1. Combine(融合函数)把三份文件的内容按差异分析自动合并成一份结果。
    Combine=三方差异融合\text{Combine} = \text{三方差异融合}
  2. ancestor(共同祖先)两个分支分叉之前的那个提交,是判断“谁改了什么”的基准。
    ancestor=分支分叉点提交\text{ancestor} = \text{分支分叉点提交}
  3. main_top(主线顶端)当前分支最新的提交。
    maintop=当前分支 HEAD\text{main}_{\text{top}} = \text{当前分支 HEAD}
  4. feature_top(特性分支顶端)被合并分支的最新提交。
    featuretop=目标分支 HEAD\text{feature}_{\text{top}} = \text{目标分支 HEAD}

冲突解决:两位作者的“争执”

如果两位作家同时改写了同一段情节,编辑就为难了:到底保留谁的写法?Git 同样如此,当两个分支在“同一文件的同一区域”各自做了不同修改时,自动合并无法进行,就产生了冲突。出现冲突时,Git 会在文件中插入特殊标记:

横线以上是当前分支(HEAD)的内容,以下是待合并分支的内容。你需要手动编辑文件,决定保留哪一边,或改写为全新内容,然后删掉所有标记。解决完冲突文件后,执行 git add 把修正后的文件放入暂存区,再 git commit 完成合并提交。也可以用 git mergetool 调用可视化合并工具,像打开一个对照编辑器来辅助解决。

冲突不可怕,它只是 Git 请你这位“主编”出面裁决。把双方稿子摆上台面,你亲手改写一个最终版本,这场风波就算过去了。记住:冲突不是错误,而是版本控制保护代码的一种方式。

Conflict  ⟺  main_diff∩feature_diff≠∅\text{Conflict} \iff \text{main\_diff} \cap \text{feature\_diff} \neq \varnothing
冲突的本质:两个分支对同一处代码的修改集合出现了交集。
  1. Conflict(冲突)自动合并无法决定保留哪份修改的状态,需要人工裁决。
    Conflict=合并中止,等待人工裁决\text{Conflict} = \text{合并中止,等待人工裁决}
  2. main_diff(主线改动集)主线相对共同祖先修改过的内容集合。
    main_diff=主线分支的改动集合\text{main\_diff} = \text{主线分支的改动集合}
  3. feature_diff(分支改动集)特性分支相对共同祖先修改过的内容集合。
    feature_diff=特性分支的改动集合\text{feature\_diff} = \text{特性分支的改动集合}
  4. 交集非空两方改了同一处,Git 不能替你拍板。
    main_diff∩feature_diff≠∅\text{main\_diff} \cap \text{feature\_diff} \neq \varnothing
text5 行
<<<<<<< HEAD主线版本的写法=======分支版本的写法>>>>>>> feature

撤销操作:时光机与后悔药

作家写砸了一章怎么办?两个选择:坐上时光机回到昨天重写,或保留今天的内容、另写一份“勘误”来抵消错误。Git 两种后悔药都有。git reset 把 HEAD 指针向后移动,回到任意旧提交。它有三个力度:--soft 只移动指针;--mixed(默认)同时重置暂存区;--hard 连工作区一起重置——这是最彻底的“回到昨天”,但未提交的修改会永久丢失。

git revert 则不同:它不动历史,而是生成一个“反向提交”来撤销某次提交的效果。这适合已经推送到远程、别人可能已拉取过的提交。另外,git checkout -- <file> 可以单独丢弃工作区某个文件的修改,把文件恢复到最近一次提交的状态——相当于只撕掉桌上这张画坏的稿纸,档案柜分毫不动。

时光机(reset)会把日历直接翻回昨天,桌上的涂改也会一并消失;勘误书(revert)则让今天的册子继续摆在架上,另加一册说明“此页内容作废”。选择哪种,取决于你需要“改写过去”还是“发布更正”。

HEADnew=HEADold−n commits\text{HEAD}_{\text{new}} = \text{HEAD}_{\text{old}} - n \ \text{commits}
reset 的本质是把当前所在位置回退 n 个提交;revert 则是新增提交来抵消历史。
  1. HEAD(当前位置)指向当前分支最新提交的指针,也就是作家正按着的那条线。
    HEAD=当前分支指针\text{HEAD} = \text{当前分支指针}
  2. 回退深度(-n commits)往回数 n 个提交,reset 的目标位置。
    回退深度 n∈N\text{回退深度 } n \in \mathbb{N}
  3. soft / mixed / hard三个力度档位,分别决定是否重置暂存区和工作区。
    力度∈{soft, mixed, hard}\text{力度} \in \{\text{soft, mixed, hard}\}

协作基础:合著者的共享书房

一部畅销小说往往不只一人创作。多人合著时,每个人书房里都有份手稿,要互相传递最新章节。Git 的远程仓库,就是团队共用的“共享书房”。git remote add origin <url> 把本地仓库和远程仓库关联起来,origin 是远程仓库的默认代号。git push 把本地新增的提交上传到远程;git pull 把远程的新提交拉到本地并自动合并。

git pull 实际上是 git fetch(下载远程内容)加 git merge(合并到当前分支)的组合。多人协同时,每个人都应该先 pull 再 push,以尽量减少冲突面。远程仓库还充当着“异地备份”的角色——即使本地电脑损坏,代码也能从远程完整恢复。在 AI 项目中,团队的实验代码、数据预处理脚本、模型部署配置文件,都依赖这套协作机制来保持“唯一的真相”。

现在,每一位合著者都能从共享书房借回最新章节,也能把自己写好的册子放回去。分支用来隔离每个人的写作进度,push/pull 用来交换成果,merge 用来汇总——一套完整的创作协作链就此闭合。

push:Local→Remote,pull:Remote→Local\text{push}: \text{Local} \to \text{Remote}, \qquad \text{pull}: \text{Remote} \to \text{Local}
本地与远程之间通过 push / pull 双向同步,这就是分布式协作的核心回路。
  1. Local(本地仓库)你自己书房里的档案柜,保存着你个人的全部提交。
    Local=本机 Git 仓库\text{Local} = \text{本机 Git 仓库}
  2. Remote(远程仓库)团队共享的中心书房,常见于 GitHub、GitLab 等平台。
    Remote=远程服务器仓库\text{Remote} = \text{远程服务器仓库}
  3. push(推送)把本地新提交上传到远程,让合著者看到你的进展。
    push:Local→Remote\text{push}: \text{Local} \to \text{Remote}
  4. pull(拉取)把远程新提交下载到本地并自动合并进当前分支。
    pull:Remote→Local\text{pull}: \text{Remote} \to \text{Local}

AI 应用场景

AI 模型实验管理

阶段:模型开发与实验迭代

解决的问题:深度学习训练需要反复调参,传统方式靠手动复制代码文件与参数记录,无法追溯到每次效果背后的准确代码和配置。Git 的分支 + 提交可以在每个实验关键节点留下完整快照,让“这个结果来自哪份代码”永远有据可查。

具体案例:算法工程师小张负责优化 BERT 微调效果。她为每个实验版本创建分支 exp/lr-2e5、exp/batch-32,每次调参后 add + commit 并附带实验描述。一周后回看 git log,轻松定位最优实验分支,并用 git checkout 回到效果最好的提交。

模型服务发布与回滚

阶段:模型部署与运维

解决的问题:模型上线后若遭遇效果劣化,需要用 git revert 或 git reset 快速回滚到上一版本。传统做法是手工替换模型文件和代码,容易漏改或出错。

具体案例:推理服务团队的测试发现新部署的推荐模型召回率下降。运维工程师用 git log 定位上次稳定提交,执行 git revert 生成反向修正提交,完成快速回滚,全程不过几分钟,线上服务几乎无感知。

多人协作的特征工程

阶段:数据预处理与特征工程

解决的问题:多个数据科学家同时修改特征脚本会产生相互覆盖。Git 分支让每个人在独立分支工作,最后 merge 汇总,用冲突标记来协调特征定义的分歧。

具体案例:一位工程师新增“用户活跃度”特征,另一位同时修改了数据清洗逻辑。两人各自在 feature 分支提交,合并时 Git 识别出对同一文件的修改,用合并工具调和,避免了互相覆盖的问题,最终保留下两套逻辑的最佳组合。

Python 演示

python47 行
"""用 Python 封装 Git 命令,模拟一次完整的开发迭代流程。"""import osimport subprocessimport tempfile  def run(cmd, cwd):    """执行 shell 命令,失败时抛出异常,成功时返回输出文本。"""    result = subprocess.run(cmd, shell=True, capture_output=True,                            text=True, cwd=cwd)    if result.returncode != 0:        raise RuntimeError(result.stderr.strip())    return result.stdout.strip()  def main():    # 在临时目录中搭建一个 Git 仓库    with tempfile.TemporaryDirectory() as repo:        run("git init -b main", cwd=repo)        run('git config user.name "AI Teacher"', cwd=repo)        run('git config user.email "teacher@example.com"', cwd=repo)         # 写一个模拟的模型训练脚本        train_py = os.path.join(repo, "train.py")        with open(train_py, "w", encoding="utf-8") as f:            f.write("def train():\n    print('training')\n")        run("git add train.py", cwd=repo)        run('git commit -m "add train script"', cwd=repo)         # 创建特性分支:调参实验        run("git branch exp/lr-2e5", cwd=repo)        run("git switch exp/lr-2e5", cwd=repo)        with open(train_py, "a", encoding="utf-8") as f:            f.write("def lr_schedule():\n    print('lr 2e-5')\n")        run("git add train.py", cwd=repo)        run('git commit -m "add lr schedule"', cwd=repo)         # 切回主线并快进合并        run("git switch main", cwd=repo)        print(run("git merge exp/lr-2e5", cwd=repo))         # 查看提交历史        print(run("git log --oneline", cwd=repo))  if __name__ == "__main__":    main()
目的:用 Python 的 subprocess 封装 Git 命令,在临时目录里自动跑完 init、config、add、commit、branch、switch、merge、log 的完整工作流,帮助初学者直观看到每一步命令的输出。
  1. result = subprocess.run(cmd, shell=True, capture_output=True, text=True, cwd=cwd)执行命令并捕获标准输出,失败时凭返回码抛错。
  2. with tempfile.TemporaryDirectory() as repo:创建临时目录作为实验场,程序结束后自动清理。
  3. run("git init -b main", cwd=repo)初始化仓库,-b main 让默认分支命名为 main。
  4. run('git config user.name "AI Teacher"', cwd=repo)设置提交身份,没有身份信息 Git 会拒绝提交。
  5. run("git add train.py", cwd=repo)把脚本从工作区放入暂存区。
  6. run('git commit -m "add train script"', cwd=repo)生成第一个提交快照。
  7. run("git branch exp/lr-2e5", cwd=repo)为调参实验创建独立分支。
  8. run("git switch exp/lr-2e5", cwd=repo)切换到实验分支,工作区文件随之更新。
  9. print(run("git merge exp/lr-2e5", cwd=repo))把实验分支合并回主线,这里触发的是快进合并。
  10. print(run("git log --oneline", cwd=repo))一行一条简洁查看提交历史。

常见误区

误区我执行了 `git commit`,以为和网盘“同步”一样,代码立刻安全地保存在了某个云端服务器上。

正解commit 只是把快照写进本地版本库,相当于把册子放进自己书房的档案柜。你的队友、GitHub 什么都看不到,必须再执行 git push 才会同步到远程。档案柜存在自己家,不等于出版社已经拿到了稿子。

为何常见:习惯了网盘、云笔记的“自动同步”体验,容易默认 Git 也是联网的,忽略了“本地仓库”和“远程仓库”是两个完全不同的概念。

误区文件已经被 `git add` 了,应该就算“保存好”了,不 commit 也行。

正解add 只是把文件放进暂存区(收藏盒),它还不是一份正式的历史快照。如果此时修改文件并再次 add,收藏盒里只会留下最新内容。真正的“存档”必须由 commit 完成——只有 commit 之后,这次状态才进入版本库,可被 git log 找到。

为何常见:把“暂存”误解为“保存”,类比了 Word 的 Ctrl+S,而忽略了 Git 的两阶段提交设计。

误区`git reset --hard` 只是回退代码,应该不会造成任何数据损失,和工作区编辑一样安全。

正解--hard 会同时重置工作区和暂存区,所有未提交的修改会被直接丢弃且很难恢复。相比 --soft(只动指针)和 --mixed(动指针与暂存区),--hard 是破坏力最强的。只有确定要彻底放弃当前修改时才能用它。

为何常见:看到“reset“就联想到”回退“,忽略了 --hard 这个参数实际上是在”清空桌面上的所有草稿”。

误区`rebase` 是 `merge` 的另一种叫法,作用完全一样,随便用哪个都行。

正解两者都能“把一个分支的变化并入另一个”,但历史形态完全不同。merge 生成一个合并提交,历史保留分叉;rebase 把分支上的提交“重放”到另一条分支顶端,历史呈线性但提交哈希全变了。对于共享分支,rebase 会重写历史,可能让他人的本地仓库“错乱”。

为何常见:新手只看到两者最终结果相似,不易察觉提交历史结构和哈希层面的本质差异。

误区删掉一个分支,分支上写的所有代码就都没了,非常危险。

正解如果分支已被合并进主线,删除分支只是拔掉那面“小旗”(指针),提交已经存在于当前分支的历史里,代码一条都不会丢。如果分支从未合并,Git 会给出警告;强制删除后提交会变成“悬空对象”,短期内仍可通过 git reflog 找回,并非立刻永久消失。

为何常见:把“分支”想象成“文件夹”,删除文件夹自然以为里面的内容也没了——但 Git 的分支只是一个轻量指针。

面试题

Q1. 在“作家书房”比喻中,暂存区对应哪一层?

单选题 · easy

  • A.桌面散落的草稿纸
  • B.挑选稿件的收藏盒← 正确答案
  • C.装订成册的档案柜
  • D.寄往出版社的邮包

答案:B。桌面草稿纸对应工作区,收藏盒对应暂存区(add 后文件所在位置),档案柜对应版本库(commit 后文件所在位置)。D 的邮包比喻远程仓库,不属于三区模型。

Q2. git add 命令的作用是什么?

单选题 · easy

  • A.把修改同步推送到远程
  • B.从工作区放入暂存区← 正确答案
  • C.从暂存区写入版本库
  • D.创建一条新的开发分支

答案:B。git add 把工作区的修改放入暂存区(收藏盒)。推送到远程是 git push,从暂存区写入版本库是 git commit,创建分支是 git branch。四个动作对应三区流转的不同环节。

Q3. 快进合并发生的必要前提是什么?

单选题 · mid

  • A.两个分支都产生了新提交
  • B.当前分支顶端是目标分支的祖先← 正确答案
  • C.两个分支修改过同一个文件
  • D.必须手动指定共同祖先

答案:B。快进合并要求当前分支没有新提交,即当前分支顶端仍然是目标分支历史上的某一点。满足此条件时,Git 只需把当前分支指针直接前移到目标分支顶端,无需生成合并提交。A 是三方合并的前提。

Q4. 切换分支时推荐使用的新命令是?

单选题 · mid

  • A.git branch
  • B.git switch← 正确答案
  • C.git merge
  • D.git restore

答案:B。较新版本的 Git 推荐用 git switch 切换分支,语义更清晰。git branch 用于创建/查看分支,git merge 用于合并,git restore 用于恢复单个文件,都不承担切换分支的职责。

Q5. git reset --hard 会重置哪些位置?

单选题 · mid

  • A.只回退 HEAD 指针
  • B.回退 HEAD 与暂存区
  • C.回退 HEAD、暂存区与工作区← 正确答案
  • D.新增一条反向修正提交

答案:C。--hard 是 reset 力度最大的一档,同时重置 HEAD、暂存区和工作区,所有未提交的修改会被丢弃。--soft 只动指针,--mixed 动指针和暂存区,D 描述的是 git revert。

Q6. 解决合并冲突时,需要在文件中寻找哪种标记?

单选题 · mid

  • A.文件中的 TODO 注释
  • B.冲突标记 <<<<<<<← 正确答案
  • C.提交信息里的 WIP 字样
  • D.代码里的 debug 打印

答案:B。Git 发生冲突时,会在冲突文件中插入 <<<<<<< HEAD、=======、>>>>>>> 分支名 三组标记,区分当前分支与合并分支的内容。找到标记后手动编辑保留所需代码,再删掉标记并提交。

Q7. 下述哪种情况会触发三方合并?

单选题 · hard

  • A.主线没有任何新的提交
  • B.两个分支各自产生新提交← 正确答案
  • C.删除分支之后的恢复过程
  • D.单条主线上的历史回退

答案:B。三方合并发生的前提是两个分支都在分叉点之后各自产生了提交。Git 需要取出共同祖先、当前分支顶端、目标分支顶端三份内容进行融合。A 属于快进合并,C、D 与合并机制无关。

Q8. git pull 的完整动作是什么?

单选题 · easy

  • A.只下载远程分支的更新
  • B.先 fetch 再自动执行 merge← 正确答案
  • C.把本地提交推送到远程
  • D.清除本地尚未提交的修改

答案:B。git pull 是 git fetch(下载远程内容)加 git merge(合并进当前分支)的组合。A 描述的是裸的 fetch,C 是 push,D 是 checkout -- <file> 或 clean 的行为。

Q9. 下列哪些属于撤销类操作?

多选题 · mid

  • A.使用 git reset 回退指针← 正确答案
  • B.使用 git revert 新增修正← 正确答案
  • C.使用 git push 同步提交
  • D.使用 git checkout 恢复文件← 正确答案

答案:ABD。git reset 回退提交指针,git revert 通过新增反向提交撤销历史效果,git checkout -- <file> 丢弃工作区单文件修改,三者都是撤销手段。git push 是把本地提交同步到远程,属于协作操作而非撤销操作。

Q10. 关于 Git 提交历史,正确的说法有哪些?

多选题 · hard

  • A.每次提交都有唯一哈希← 正确答案
  • B.本地提交不会自动同步← 正确答案
  • C.分支删除后提交永久消失
  • D.提交作者来自本地配置← 正确答案

答案:ABD。每个 commit 都由内容、父提交、作者等信息生成唯一 SHA-1 哈希;本地 commit 只存在于本地,必须 push 才到远程;作者信息来自 git config。C 错误:已合并的分支删除后,提交仍存在于主线历史中,不会消失。