想象一家美食出版社,要集结全城一百位业余厨师,共同编撰一本《社区美食大全》。主编手里握着一本“定稿本”,锁在出版社总部的保险柜里;各位厨师则在自己家中各自创作菜谱,互不干扰。当一位厨师觉得自己的红烧肉配方足够成熟时,他不会冲进出版社、撕下定稿本直接重写——而是写一封结构完整的“投稿函”,说清楚自己写了什么、为什么这么写、如何验证口味。编委会收到投稿后,先让“格式校对机器人”检查一遍格式是否规范,再请几位资深评审逐字审阅。评审点头后,主编才把这份菜谱正式誊写进定稿本。万一两位厨师恰好都写了“红烧肉”且版本不同,编辑部就得召开碰头会,协商决定保留哪一版。这就是现代软件团队用 Git 做远程协作、用 Pull Request(PR)管理代码合入的全过程。接下来,我们跟随这个“合著菜谱”的故事,把远程仓库、分支策略、PR 流程、代码评审、CI 检查与冲突解决一次看明白。

岗位关联

Pull Request 是团队协作的核心枢纽:掌握它,你才能从“一个人写代码”进化到“和一群人安全高效地写同一套代码”,这是任何正规软件团队的日常基本功。

目录
  1. 可视化演示
  2. 远程仓库协作
  3. 分支管理策略
  4. PR创建与提交
  5. 代码评审机制
  6. CI自动化检查
  7. 合并与冲突解决
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示分支创建、代码提交、PR审查、合并部署的完整远程协作工作流。

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

分支创建 从main分支创建feature分支 隔离开发互不干扰 git checkout -b feature/xxx

远程仓库协作

出版社的定稿本锁在总部保险柜里,厨师们在家写菜谱。新来的厨师不会自己闷头重写菜谱,而是先到总部复印一份当前定稿本,然后对着复印件创作——这就是“克隆”。在 Git 世界里,这本“定稿本”叫远程仓库,通常托管在 GitHub、GitLab 这类平台上,而“复印”的动作就是 git clone。克隆之后,你的电脑上就有了一个和远程完全一样的本地仓库,还自动记录了和远程仓库的联系方式,方便随时同步。

复印只是开始。厨师在家把辣子鸡写进自己的笔记本后,想让它进入团队共享的定稿本,就要“投稿”——用 git push 把本地提交推送到远程仓库。反过来说,如果主编在总部誊写了别人的新菜谱,你隔天想把这些更新拿回家,就要“领取”——用 git pull 把远程的新提交拉取到本地。可是,当你投稿时发现远程已经有了别人先推送的新内容,出版社可不允许你直接覆盖——Git 会提示“推送被拒绝”,因为你的历史落后于远程。这时候的正确做法是先 git pull,把自己的笔记基于最新定稿本重新整理,再推上去。

除了 clone 和 push/pull,GitHub 还提供一种特殊的“复刻”方式:fork。它像你从出版社借了一本复印本,放到自己家里单独保管——你可以随意修改,改完后再向官方仓库发起投稿。这样,没有推送权限的贡献者也能安全地参与项目。这套机制让厨师们各安其位:有人在家里安心创作,有人定期去总部取新菜谱,整个编辑部有条不紊。

git pull=git fetch+git merge\displaystyle \text{git pull} = \text{git fetch} + \text{git merge}
git pull 不是一个魔法命令,而是两个动作的合体:先“取回”远程最新提交,再把它们和你本地的改动“合并”到当前工作分支。
  1. fetch(取回)这一步只把远程仓库的新提交下载到本地一个“只读的远程跟踪区”,不会触碰你正在写的工作区,就像去总部把新菜谱复印回来,先放在自己书桌上。
    fetch:origin/main→refs/remotes/origin/main\displaystyle \text{fetch} : \text{origin/main} \to \text{refs/remotes/origin/main}
  2. merge(合并)把取回的提交真正合入你当前正坐在面前的分支里,你的工作区会随之更新,好比把新菜谱的内容抄进自己的笔记本。
    merge:refs/remotes/origin/main→HEADlocal\displaystyle \text{merge} : \text{refs/remotes/origin/main} \to \text{HEAD}_{local}

分支管理策略

出版社给每位厨师发了专属工作台,台面上贴着标签:“川菜·王师傅”“甜点·李师傅”。大家互不占用彼此台面,想怎么试验都行——这就是分支(branch)。在 Git 中,每次提交都会沿着一条线排队,分支就是这条线上的一个“工作台”。新功能开发时,团队会从 main 主干拉出一个特性分支(feature branch),比如 feature/login、fix/typo,命名规范一看就知道这桌在做哪道菜。

主干 main 可不是普通工作台,它是出版社的“金库”:定稿本最后要誊写的版本。为了安全,主流平台支持“保护分支”(protected branch)策略:main 分支禁止任何人直接推送,所有改动必须通过 PR 提交、经过评审与 CI 检查后才能合入。特性分支则完全开放,因为这里只是试验田,改坏了删掉重建即可。这种“主干受保护、特性随意开”的结构,让几十、上百人可以在不同的工作台上并行切菜炒菜,互相不干扰。

“主干策略”(trunk-based development)讲究小步快跑:每个特性分支尽量小、生存期尽量短,频繁合入主干,让主干始终保持“可发布”状态。分支命名规范不只是美观,它让整个团队的 PR 列表一目了然——哪些是修 bug、哪些是新功能、哪些是研究性实验,像出版社的分工牌一样,让每一桌的工作内容透明可见。

branchfeature→PR 评审branchmain\displaystyle \text{branch}_{\text{feature}} \xrightarrow{\text{PR 评审}} \text{branch}_{\text{main}}
特性分支是起点,主干是终点,中间的箭头不是普通的 merge,而是“带门禁的 PR 通道”——评审与 CI 就是这扇门的两把锁。
  1. 起点 branch\_feature开发者基于主干最新版本新建的特性分支,如 feature/pay,改动都提交在这里。
    branchfeature=mainlatest+new commits\displaystyle \text{branch}_{\text{feature}} = \text{main}_{\text{latest}} + \text{new commits}
  2. 门禁 PR 评审开发者发起 Pull Request,请求把特性分支合并进主干,等待人工评审和自动检查双重放行。
    PR={review≥1, CI=pass}\displaystyle \text{PR} = \{\text{review} \ge 1,\ \text{CI} = \text{pass}\}
  3. 终点 branch\_main门禁通过后,feature 分支的提交被合并到受保护的 main 分支,主干前进到新版本。
    branchmain+=branchfeature\displaystyle \text{branch}_{\text{main}} \mathrel{+}= \text{branch}_{\text{feature}}

PR创建与提交

投稿不是口头说一句“我写好了”,而是要递上一封格式完整的投稿函。GitHub 的 Pull Request 就是程序员界的投稿函:它把一个分支上的所有提交打包成一份“待审请柬”,发给团队里所有人看。PR 写得好不好,直接决定评审人愿意花多少精力帮你把代码质量抬上去。好的 PR 开头就讲清楚“这道菜为什么做、给谁吃、怎么验证味道”。

为了让投稿函标准化,团队会在仓库里放一个 PR 模板文件(.github/pull_request_template.md)。模板强制要求写清楚:背景(为什么改)、改动方案(怎么改的)、测试验证(我跑了什么)。这像投稿函的固定栏目——“菜谱名”“用料”“步骤”“品鉴结果”,缺一不可。同时,PR 还应该关联 Issue(问题单):在描述里写一句 Closes #123,合入后这个 Issue 会自动关闭,就像投稿函末尾附一行“本文案解决的是读者第 123 号来信”。

提交规范同样重要。提倡遵循 Conventional Commits 风格:feat: 表示新功能,fix: 表示修 bug,docs: 表示文档改动。这样自动生成 changelog 和后续定位问题都很方便。一封写得明明白白的投稿函,不仅让评审人省心,也会让几个月后的自己回看历史时轻易读懂当时的设计意图。

PR={title, description, commits, target_branch}\displaystyle \text{PR} = \{ \text{title},\ \text{description},\ \text{commits},\ \text{target\_branch} \}
一个规范的 PR 包含四要素:一句话标题、详细的评审说明、所携带的提交集合、以及目标分支。缺了任何一项,评审人都难以高效工作。
  1. title(标题)用一句话概括本次改动,例如“feat: 新增登录页面”,评审人扫一眼列表就知道这个 PR 在做什么。
    title=Conventional_Style+summary\displaystyle \text{title} = \text{Conventional\_Style} + \text{summary}
  2. description(说明)解释改动背景、实现思路、测试方法和可能的风险点,避免评审人读完整份代码还猜不透意图。
    description⊃{why, how, test}\displaystyle \text{description} \supset \{\text{why},\ \text{how},\ \text{test}\}
  3. commits(提交集合)PR 会携带特性分支上的若干提交,提交信息要清晰,因为小提交比大杂烩更容易审查。
    commits={c1,c2,…,cn}\displaystyle \text{commits} = \{c_1, c_2, \dots, c_n\}
  4. target\_branch(目标分支)默认指回受保护的 main 分支,必须仔细确认,投错门可会让代码进错仓库。
    target_branch=main\displaystyle \text{target\_branch} = \text{main}

代码评审机制

美食编辑社有“大师盲评”制度:一道关键的菜谱投稿,要请三位资深厨师分别品评,各自写下“通过”或“建议修改”。代码评审(Code Review)正是这套机制在软件世界的投影。评审人不会只盯着“代码能不能跑”,而是关注更本质的问题:这个实现的逻辑是否正确?架构上是否合理?有没有安全隐患?命名是否清晰?未来好不好维护?说白了,评审是在替项目三个月后的维护者把关。

GitHub 允许仓库用 CODEOWNERS 文件指定“谁拥有哪些路径”。比如 /src/inference/ 下的代码必须由懂模型推理的两位资深工程师批准才能合入,其他区域则由前端负责人把关。这像川菜章节必须由川菜大师签字确认,甜点章节只听甜点主厨的意见。当评审人提交"Request Changes"(请求修改)时,PR 会锁死无法合并,必须等作者修改后重新提交评审;直到至少一位有权限的评审人点了 Approve(批准),合并之门才打开。

评审意见通常分为“阻塞性问题”和“风格建议”:前者不解决不能合并,后者只是锦上添花。很多团队把 nit: 前缀当作“挑个小刺,你可以不改”的暗号,减少无谓的来回拉扯。评审不是走形式,更不是找茬,而是让每个人在提交代码前多想一步——就像编辑部的三盲评审,本质是保护这本全书的质量底线,而不是刁难某个厨师。

mergeallowed=reviewapproved∧cisuccess\displaystyle \text{merge}_{\text{allowed}} = \text{review}_{\text{approved}} \wedge \text{ci}_{\text{success}}
合并的闸门有两把钥匙:人工评审批准 + 自动化 CI 通过。两把必须同时到位,缺一不可——这是 PR 流程的安全底线。
  1. review\_approved(评审批准)至少一位具备权限的评审人在 PR 上给出 Approve 意见,代表人工层面的质量背书。
    reviewapproved:∃reviewer∈CODEOWNERS\displaystyle \text{review}_{\text{approved}} : \exists \text{reviewer} \in \text{CODEOWNERS}
  2. ci\_success(CI 通过)所有自动化检查(构建、测试、静态检查)状态为绿色通过,代表机器层面的最低门槛。
    cisuccess=⋀∀jobjob=pass\displaystyle \text{ci}_{\text{success}} = \bigwedge_{\forall \text{job}} \text{job} = \text{pass}
  3. 合取关系两个条件用逻辑 AND 连接,意味着任何一条不满足,整个合并操作都不被允许。
    mergeallowed=⊤  ⟺  review=⊤∧ci=⊤\displaystyle \text{merge}_{\text{allowed}} = \top \iff \text{review} = \top \land \text{ci} = \top

CI自动化检查

出版社有一台“格式校对机器人”:每位厨师的投稿递进来,先送进流水线自动检查——错别字、排版、步骤完整性,全部合格才进入人工评审。在软件世界,这台机器人叫 CI(Continuous Integration,持续集成)。它的理念是:代码每发生一次变更,就自动触发一次完整的检查和验证,确保改动没有破坏已有功能。CI 通常配置在 GitHub Actions、GitLab CI 等平台上,仓库里放一份 workflow 配置文件,之后每次 push 或 PR 都会自动跑起来。

一个典型的 CI 流程包含三层检查:自动构建(Build),把源代码编译打包,验证“能跑起来”;单元测试(Test),运行一连串自动化测试,验证“行为对不对”;静态检查(Lint),用工具扫描代码风格和常见坏味道,验证“写得好不好”。跑完的结果会以一个“状态徽章”展示在 PR 页面上——绿色代表通过,红色代表失败。配合保护分支策略,CI 一旦失败,合并按钮就处于禁用状态,谁也绕不过去。

CI 存在的意义不是取代人工,而是把机械性的验证工作交给机器,把评审人的精力解放出来,专注在“设计合理吗”这类真正需要思考的问题上。就像格式校对机器人先扫掉错别字,人工评审才能专心品评菜品的火候与创意。一个设置良好的 CI 管线,是团队安全感的最大来源——它让你相信:每次合并都建立在坚实的地基上,而不是盲目的信任。

CI=build∧test∧lint\displaystyle \text{CI} = \text{build} \wedge \text{test} \wedge \text{lint}
CI 是三层递进的安全网:先确保能构建,再确保功能正确,最后确保风格一致。三者合成一行绿色勾勾。
  1. build(构建)把源代码编译成可运行产物,这一步失败说明代码存在语法错误或依赖缺失,后面不用再测了。
    build=compile(source)→artifact\displaystyle \text{build} = \text{compile}(\text{source}) \to \text{artifact}
  2. test(测试)在构建产物上运行测试套件,验证核心函数、接口、页面行为是否符合预期,捕获功能回归。
    test=⋃i=1nassert(behaviori)\displaystyle \text{test} = \bigcup_{i=1}^{n} \text{assert}(\text{behavior}_i)
  3. lint(静态检查)用 ESLint、Ruff 等工具扫描代码,检查未使用变量、缩进混乱等问题,让团队风格统一。
    lint=find(bad_pattern)=∅\displaystyle \text{lint} = \text{find}(\text{bad\_pattern}) = \varnothing

合并与冲突解决

稿子通过评审后,主编把它誊写进定稿本——这就是合并(merge)。但在誊写时,如果两位厨师都改写了“糖醋排骨”的同一段步骤,主编就傻眼了:两版文字放在同一个位置,该抄哪一版?这就是 Git 冲突(conflict)。冲突的本质是多人修改了同一区域的内容,Git 无法自动判断谁对谁错。它不像复制文件那样简单——复制文件时,同名文件会自动覆盖,但在代码合并里,同一段代码被两个人改成不同的样子,机器无法替你拍板。

Git 提供三种常见的合并方式。merge commit 保留所有人的提交历史和分叉结构,像一张真实的时间地图;squash merge 把特性分支上的所有提交压缩成一个,主干历史干净得像重新誊写过的抄本;rebase merge 则把特性分支的提交“摘下来”,按时间顺序嫁接回主干最新点之后,让提交历史变成一条漂亮的直线。当出现冲突时,最稳妥的流程是:先 pull 获取主干最新内容,然后 rebase 自己的分支,找到冲突标记(<<<<<<< 与 >>>>>>> 之间的区域),手动协商保留哪段代码,删掉标记后重新提交。

rebase 实践中最重要的一条铁律是:不要对共享分支 rebase。分支已经推送到远程并被别人拉去用了,再重写它的历史,会让所有上下游同事同步时混乱不堪,就像出版社已经印刷了一版书,又要偷偷撕掉重排。冲突不是灾难,而是团队协作里再正常不过的信号——它提醒你“这段代码很重要,值得大家认真协商”。学会规范地解决冲突,等于掌握了团队协作中最关键的一门手艺。

rebase=replay(commitsfeature→new_base)\displaystyle \text{rebase} = \text{replay}(\text{commits}_{\text{feature}} \to \text{new\_base})
rebase 像把一叠便利贴从旧墙上揭下来,按顺序重新贴到刷好漆的新墙上。冲突就是某张便利贴的旧位置和新墙上的内容“叠加”了,需要你亲手调一下位置。
  1. 旧基线特性分支上的提交原本基于旧版主干,这些提交记录里保存着它们各自的“前一个提交”指针。
    commitsfeature={c1,c2,…,cn∣ci.parent∈old_base}\displaystyle \text{commits}_{\text{feature}} = \{c_1, c_2, \dots, c_n \mid c_i.\text{parent} \in \text{old\_base}\}
  2. 重放(replay)Git 遍历特性分支的每个提交,按顺序把它们重新应用到最新的主干基底之上,每应用一个相当于重新执行一次改动。
    apply(ci,new_basei)⇒new_basei+1\displaystyle \text{apply}(c_i, \text{new\_base}_i) \Rightarrow \text{new\_base}_{i+1}
  3. 新基线当所有提交依次重放完毕,特性分支就“站”在了主干最新位置之上,历史变为一条平滑直线。
    new_base=mainlatest,history=linear\displaystyle \text{new\_base} = \text{main}_{\text{latest}},\quad \text{history} = \text{linear}

AI 应用场景

AI团队的训练代码PR评审

阶段:代码开发与协作

解决的问题:机器学习团队的训练代码(数据预处理、模型定义、超参数配置)往往容错率极低,一行数据对齐错误会导致整个实验失真。传统做法是大家在共享服务器上互相覆盖文件,出了问题难以追溯;基于 PR 的流程则让每次模型代码改动都经过评审和 CI 检查,自动跑冒烟测试和数据集校验,确保实验可复现。

具体案例:某算法团队在 GitHub 上管理一个模型训练仓库,所有数据处理脚本和训练配置都通过 PR 提交。每次 PR 自动触发 CI,运行一个小型样本集的训练任务,验证 loss 是否正常下降;同时 CODEOWNERS 指定两位资深算法工程师审批所有涉及数据管道的改动。这样一来,模型的每次“进化”都有迹可循,新人也不会再悄悄踩坏别人的实验环境。

依赖更新机器人自动化PR

阶段:环境维护与依赖升级

解决的问题:AI 项目依赖大量第三方库,依赖版本过旧会带来安全漏洞,但盲目升级又可能破坏模型推理或训练性能。GitHub 的 Dependabot 机器人会自动检测依赖更新并创建 PR,CI 则负责验证新依赖下代码是否仍能通过全部测试。传统做法是人工不定期检查,容易漏掉关键安全更新。

具体案例:一个生产环境的 AI 推理服务,通过 Dependabot 自动收到了 numpy 安全更新 PR。CI 自动运行了全部单元测试和模型加载冒烟测试,在发现旧代码使用了已弃用的 API 后,开发人员在 PR 中修复,然后合并上线。整个流程从发现漏洞到修复,全程透明、可审计,没有一次“凌晨紧急上线”。

Python 演示

python78 行
#!/usr/bin/env python3"""模拟一个带冲突检测与门禁的PR合入流程"""import hashlibfrom collections import namedtuple  Commit = namedtuple("Commit", ["author", "message", "parent"])  def commit_hash(c):    raw = f"{c.author}|{c.message}|{c.parent}".encode()    return hashlib.sha1(raw).hexdigest()[:8]  class PullRequest:    """一个PR:携带提交、状态、评审与CI结果"""     def __init__(self, title, author, commits, base_head):        self.title = title        self.author = author        self.commits = commits        self.base_head = base_head        self.reviews = []        self.ci_status = "pending"        self.merged = False     def add_review(self, reviewer, approve):        self.reviews.append((reviewer, approve))     def run_ci(self, trunk):        """CI检查:代码包含fix字样则失败"""        upstream = {c.hash for c in trunk}        failed = [            c for c in self.commits            if c.parent not in upstream and "merge" not in c.message        ]        self.ci_status = "failure" if failed else "success"        return self.ci_status     def merge(self, trunk):        if self.ci_status != "success":            raise RuntimeError("CI未通过,禁止合并")        if not all(ok for _, ok in self.reviews):            raise RuntimeError("评审未通过,禁止合并")        base_pos = next(            i for i, c in enumerate(trunk)            if c.hash == self.base_head        )        trunk[base_pos + 1:base_pos + 1] = self.commits        self.merged = True        return f"PR「{self.title}」已合入,主干提交数 = {len(trunk)}"  def simulate():    trunk = [Commit("alice", "init project", None)]    for c in trunk:        c.hash = commit_hash(c)     pr1 = PullRequest("实现登录页", "bob",                      [Commit("bob", "add login page", trunk[-1].hash)],                      trunk[-1].hash)    pr1.run_ci(trunk)    pr1.add_review("carol", True)    print(pr1.merge(trunk))     pr2 = PullRequest("重构推荐逻辑", "dave",                      [Commit("dave", "fix recommend", trunk[-1].hash)],                      trunk[-1].hash)    print("CI状态:", pr2.run_ci(trunk))    pr2.add_review("bob", True)    try:        print(pr2.merge(trunk))    except RuntimeError as e:        print("拦截:", e)  if __name__ == "__main__":    simulate()
目的:用一个小型 Python 脚本模拟 PR 的完整生命周期——两个开发者分别创建 PR,一个通过 CI 和评审顺利合并,另一个因提交信息含 “fix“ 而被 CI 判定失败,演示”门禁”如何拦截不合格改动。
  1. class PullRequest定义 PR 的核心数据结构:谁提的、带了哪些提交、目标基线指向主干哪个位置。
  2. def run_ci(self, trunk)模拟 CI 检查:如果某个提交的父提交不在主干历史中,判定为结构异常,返回 failure。
  3. def merge(self, trunk)合并闸门:先检查 CI 状态、再检查所有评审是否批准,两项全过才允许合入主干。
  4. base_pos = next(...)在主干中定位目标基线的下标位置,确定新提交应该插在历史线的哪个节点之后。
  5. trunk[base_pos + 1:base_pos + 1] = self.commitsPython 切片赋值实现“原地插入”:把 PR 的提交序列拼接到主干对应位置之后。
  6. pr2.run_ci(trunk)开发者 dave 的 PR 因为提交信息写的是 “fix recommend“,被模拟 CI 判定为”含未修复模式”,拦截合入。

常见误区

误区我花两周做了一大块功能,写成一个大 PR 一起提交,评审人一次看完,多高效。

正解大 PR 是评审的灾难。评审人面对几百行变更往往难以集中注意力,容易漏掉关键问题;合并时也更容易产生冲突。正确做法是把大功能拆成多个小而独立的 PR,每个 PR 只做一件事,像写书的每一章独立成篇,审完一章再写下一章。小 PR 的评审速度快、合并风险低,历史记录也更清晰。

为何常见:人们把 PR 误解为“提交的最终容器”,觉得一次提交省事,却没有意识到 PR 的核心价值是“让每次合入都可控、可审查”。

误区我们团队一共就三个人,直接 push 到 main 最省事,走 PR 太官僚了。

正解保护分支和 PR 不只是为大团队设计的。即使人少,PR 也能提供审计记录、评审缓冲和自动化检查的触发点。一次“图省事”的直接推送,可能悄悄引入一个 bug 而不自知,等发现问题已经过了十次提交。哪怕你一个人写项目,养成“分支 + PR + 合并”的习惯,回头看历史时也事半功倍。

为何常见:流程的价值在短期内看不出来,新人总觉得“多此一举”,直到踩坑才后悔没有门禁。

误区CI 全绿了,说明代码经过验证了,可以放心合并,不用浪费时间评审。

正解CI 只验证“机器能验证的东西”——能构建、能通过预设测试、风格一致。但测试覆盖不到的问题(比如糟糕的接口设计、性能隐患、安全逻辑缺陷)依然存在。评审人看的是代码的意图与设计,这是 CI 永远替代不了的。机器把关底线,人把关高度,两者是互补关系。就像格式校对机器人查出的是错别字,而不是“这道菜是否真的好吃”。

为何常见:绿勾勾给人强烈的安全感,人们容易把“测试通过”偷换成“代码没有问题的充分条件”。

误区rebase 时冲突好烦,反正我的代码是新的,直接 force push 把远程覆盖了就行。

正解force push 会重写远程历史,等于告诉整个团队“我之前推的都不算数”。如果别人已经基于旧历史做了开发,他们下次拉取时就会遭遇大规模混乱。正确做法永远是:先 pull 最新主干,rebase 自己的分支,耐心解决冲突标记里的每一段代码,再正常推送。只有在你确认该分支只有自己一个人使用时,才允许 force push。

为何常见:冲突发生时人的第一反应是“消灭问题”,而 force push 是最粗暴的“消灭”方式,却忽略了它只是把问题转嫁给了队友。

面试题

Q1. git pull 等价于以下哪两个 Git 命令的组合?

单选题 · easy

  • A.git fetch 和 git merge← 正确答案
  • B.git clone 和 git commit
  • C.git push 和 git rebase
  • D.git branch 和 git checkout

答案:A。git pull 的本质是先从远程取回最新提交(fetch),再合并到当前分支(merge)。这正是“从总部复印新菜谱,再抄进自己笔记本”的两个动作。clone 是首次复制仓库,commit 是本地存档,push 是反向投稿,均不构成 pull 的组成部分。

Q2. 远程仓库(如 GitHub)在团队协作中的核心作用是什么?

单选题 · easy

  • A.提供代码共享的中枢← 正确答案
  • B.取代本地 IDE 编辑器
  • C.自动修复代码的 bug
  • D.替代所有自动化测试

答案:A。远程仓库是所有开发者提交代码和拉取更新的共享中枢,像出版社总部的定稿本保险柜。它不负责编辑代码、修复 bug 或跑测试,那些由本地工具与 CI 系统完成。多人协作者各自在本地工作,通过远程仓库完成同步与协作。

Q3. 防止未完成的功能直接改动主干,最推荐的做法是?

单选题 · easy

  • A.直接在 main 分支上开发
  • B.每个功能建特性分支← 正确答案
  • C.删除远程仓库避免风险
  • D.只在正式服务器改代码

答案:B。特性分支是团队协作的黄金习惯:每个功能或修复都在独立分支上进行,完成后通过 PR 合入主干。这样主干始终保持可发布状态,未完成的功能不会影响他人。直接改 main 是“在定稿本上打草稿”,删除仓库或上服务器改代码则是因噎废食。

Q4. GitHub 中 CODEOWNERS 文件的主要用途是什么?

单选题 · mid

  • A.统计每位开发者的代码行数
  • B.指定各路径的默认评审人← 正确答案
  • C.防止所有代码被外部复制
  • D.自动生成项目文档

答案:B。CODEOWNERS 可以规则化地指定“谁对哪些路径负责”。例如 /src/model/ 由算法专家批准,/src/ui/ 由前端负责人批准。修改这些路径的 PR 会自动要求对应负责人评审。它像川菜章节必须由川菜大师点头,与统计代码行数、防盗版、查文档无关。

Q5. 一个 PR 被评审人标记为 "Request Changes" 后,正确的处理方式是?

单选题 · mid

  • A.无视意见直接点合并
  • B.修改代码后重新提交评审← 正确答案
  • C.关闭并放弃整个项目
  • D.将自己设为新的评审人

答案:B。“Request Changes“ 是评审人给出的”打回修改”意见,此时 PR 的合并按钮会被禁用(若配置了保护分支)。作者应该根据意见修改代码、推送新提交,然后请评审人再次确认。直接合并违背评审制度,放弃项目是过激反应,自任评审人则让 CODEOWNERS 规则形同虚设。

Q6. 以下哪些是常见的 PR 合并方式?(多选)

多选题 · mid

  • A.Merge commit 方式← 正确答案
  • B.Squash merge 方式← 正确答案
  • C.Rebase merge 方式← 正确答案
  • D.Delete branch 方式

答案:ABC。GitHub 提供三种标准合并方式:Merge commit 完整保留分支历史与分叉结构;Squash merge 把特性分支所有提交压缩为一个;Rebase merge 将提交重放到主干最新点,形成线性历史。Delete branch 只是在合并后删除源分支的清理动作,不属于合并方式。

Q7. CI(持续集成)在 PR 流程中主要负责什么?

单选题 · mid

  • A.人工阅读并评审代码逻辑
  • B.自动构建、测试和静态检查← 正确答案
  • C.自动将代码直接部署上线
  • D.统计每个开发者的活跃度

答案:B。CI 的核心职责是自动化验证每次代码变更:编译构建、运行测试套件、执行静态分析,把结果以状态徽章体现。它不替代人工评审,通常也不负责部署上线(那是 CD 的范畴)。活跃度统计根本不是 CI 的职能。

Q8. 解决 Git 冲突的正确步骤包括以下哪些?(多选)

多选题 · hard

  • A.拉取最新的主干内容← 正确答案
  • B.rebase 自己的特性分支到主干← 正确答案
  • C.手动修改冲突标记中的代码← 正确答案
  • D.强制推送覆盖远程所有内容

答案:ABC。标准冲突解决流程是:先 fetch/pull 获取最新主干,再 rebase(或 merge)自己的分支,在冲突文件里手动协商保留正确的代码,删除标记后继续 rebase 直至完成。force push 覆盖远程是危险动作,可能重写团队共享历史,绝不是在冲突场景下的默认选择。

Q9. git rebase 的核心作用是什么?

单选题 · easy

  • A.删除远程仓库的所有历史
  • B.把提交重放到新的基础之上← 正确答案
  • C.复制当前分支为两个分支
  • D.加密所有提交记录

答案:B。git rebase 把当前分支的提交“摘下来”,按顺序重放到另一个基础提交之上,让分支看起来像从最新基线长出来一样,历史更简洁线性。它不删除远程仓库、不复制分支、更与加密无关。用比喻说,就是把旧墙上的便利贴重新贴到刷好漆的新墙上。

Q10. 分支保护策略(Branch Protection Rules)可以强制团队遵守哪些规则?(多选)

多选题 · hard

  • A.所有改动必须通过 PR 提交← 正确答案
  • B.必须有一条 CI 检查通过← 正确答案
  • C.必须获得评审人批准← 正确答案
  • D.必须删除所有提交历史

答案:ABC。分支保护规则是为主干分支“上锁”:禁止直接推送、强制 PR 流程、要求 CI 通过、要求至少一个评审人批准,还可以锁定期限等。删除提交历史不是可配置的保护选项,反而是团队协作中极力避免的危险操作。保护分支像出版社金库的三道锁,保证任何改动都经过正规流程。