从递归求和到 Git 提交的实践笔记
从注释原则和 ValueError 出发,理解暂存区、AM 状态、git add .、origin 与 upstream,并复盘为什么推送成功仍可能只上传了空文件。
注释应补充哪些信息
一次递归求和练习引出了两个问题:怎样把代码的意图说明白,以及怎样确认写好的内容真正进入了提交。两者都需要明确约定:函数有哪些有效输入,Git 的下一次提交包含哪一版内容。
判断注释有没有价值,可以问:删掉它之后,读者是否会少知道重要的意图、约束或原因?
- 函数文档字符串说明接口约定。写清函数用途、参数要求、返回值和会抛出的异常,
help()可以展示这些说明。 - 实现旁的注释解释原因和难点。例如递推关系、终止依据、边界条件和不直观的设计选择。
- 省略对语法的逐句复述。
# 把 n 减 1紧挨着n - 1,通常没有增加信息。 - 让注释随代码一起更新。函数开始拒绝负数时,文档也要写明异常;过时的注释会误导调用者。
学习示例可以保留对基础概念的说明。随着读者熟悉语法,应把注释集中在意图、约定和推导上。
为负数输入抛出 ValueError
这里把求和函数定义在非负整数上:n == 0 返回 0,正整数按递推关系求和。负数属于约定之外的输入,应明确拒绝。如果也返回 0,调用者就无法从结果区分合法的空和与错误的参数。
raise ValueError("n 不能小于 0") 会抛出异常并离开当前函数;调用者可以捕获它,否则异常会继续向外传播。Python 官方文档将 ValueError 用于参数类型合适但值不符合要求的情形。
| 输入 | 约定的行为 |
|---|---|
recursion_sum(0) | 返回 0 |
recursion_sum(5) | 返回 15 |
recursion_sum(-1) | 抛出 ValueError |
表达式 n + recursion_sum(n - 1) 要先等递归调用返回,当前层才能完成加法。因此,“先累加 n,再递归”并不准确。
这个示例的 n: int 是类型注解;它没有额外实现运行时整数类型检查。递归版本也受解释器调用深度限制,适合用来理解执行过程。
def recursion_sum(n: int) -> int:
"""递归计算从 1 到 n(含)的整数之和。
Args:
n: 非负整数。
Returns:
从 1 到 n 的整数之和;当 n 为 0 时返回 0。
Raises:
ValueError: 当 n 为负数时。
"""
if n < 0:
raise ValueError("n 不能小于 0")
if n == 0:
return 0
# S(n) = n + S(n - 1),递归调用返回后再完成当前层的加法。
return n + recursion_sum(n - 1)工作区 暂存区 本地提交与远端仓库
编辑器里保存了文件,只能说明工作区中的文件已更新。随后还要选择提交内容、创建本地提交,再更新远端分支。
| 位置 | 存放什么 | 相关操作 |
|---|---|---|
| 工作区 | 磁盘上正在编辑的文件 | 编辑并保存 |
| 暂存区,也称 index | 准备写入下一次提交的内容 | git add |
| 本地仓库 | 提交记录和分支 | git commit |
| 远端仓库 | 通过网络同步的提交记录和分支 | git push |
这里使用的普通 git commit -m "说明" 按暂存区创建提交;git push 再更新目标远端分支。参见 git commit 官方文档。
编辑并保存文件
↓ git add
准备好下一次提交的内容
↓ git commit
形成本地提交
↓ git push
更新远端分支git add 与 git add . 的范围
git add recursion_sum.py 把这个文件执行 add 时的内容放入暂存区。后续编辑不会自动更新已经暂存的版本;如果也想提交后续修改,就要再执行一次 git add。
git add . 中的点表示当前目录。它暂存当前目录及其子目录中的新增、修改和删除;在仓库根目录执行时,范围覆盖整个仓库。被忽略的未跟踪文件通常不会加入,但已被 Git 跟踪的文件仍会参与暂存,即使后来匹配了忽略规则。
只准备提交一个文件时,明确写文件名更容易控制范围;确认目录中全部改动都属于本次提交时,再使用 git add .。参见 git add 官方文档。
git status --short
git diff
git add recursion_sum.py
git diff --cached
git commit -m "Add recursive sum with input validation"怎样读懂 AM 与前面带空格的 M
git status --short 的状态有两列。在普通文件、无合并冲突的场景中,第一列 X 比较暂存区与当前提交,第二列 Y 比较工作区与暂存区。
| X | Y | 含义 |
|---|---|---|
A | M | 新文件已暂存;工作区内容随后又改了,最新修改还没有暂存。 |
| 空格 | M | 已有文件在工作区被修改;暂存区没有相对当前提交的新变化。 |
M | 空格 | 已有文件的修改已经暂存;工作区没有额外修改。 |
A | 空格 | 新文件已经暂存,工作区与暂存区一致。 |
AM 表示“已暂存的新增文件又有未暂存修改”,本身不能说明暂存的是空文件。只有进一步检查暂存区或提交内容,才能知道其中具体保存了什么。参见 git status 的短格式说明。
AM recursion_sum.py
M recursion_sum.py
M recursion_sum.py
A recursion_sum.py为什么推送成功仍然只上传了空文件
这次练习中,新建的空文件先进入了暂存区,随后写入的代码留在工作区。提交时,Git 创建了文件,却没有新增文本行:
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 recursion_sum.py结合当时实际检查到的暂存区内容,可以确认该提交保存的是空文件。git push 成功后,远端收到的也就是这个版本。再次推送显示 Everything up-to-date,表示所推送的分支没有新的提交需要传送;工作区里仍然可以存在未提交的修改。
当时的 M recursion_sum.py 正是在提示:代码还没有重新暂存。处理方式是把当前内容放入暂存区,确认差异,再创建新提交。重复 push 无法替代这些步骤。
检查不同层次时,可以分别使用下面的命令。
git diff # 工作区相对暂存区的修改
git diff --cached # 暂存区相对当前提交的修改
git log -1 --stat # 最近一次提交涉及哪些文件
git show HEAD:recursion_sum.py # 当前提交里实际保存的文件内容origin 与 upstream 分别是什么
origin 是本地为远端仓库配置的名称,通常在克隆时创建;它对应一个仓库地址。recursion-practice 是分支名。
git push --set-upstream origin recursion-practice 把这个本地分支推送到 origin,并设置它的上游跟踪关系。--set-upstream 可以缩写为 -u。在具备推送权限、远端允许创建分支的情况下,首次推送会自动创建同名远端分支,无需先在网页上手动新建分支。
账号认证决定能否访问和修改远端仓库;建立分支本身不需要新建账号。看到 has no upstream branch,应为当前分支设置上游,而不是反复执行同一条不带目标的 git push。
origin/recursion-practice 是本地记录的远端跟踪引用。git branch -vv 展示的领先或落后关系依赖这些本地记录;需要更新远端状态时先执行 git fetch origin。参见 git remote 与 git push 官方文档。
git remote -v
git remote get-url origin
git branch -vv
# 首次推送 recursion-practice,并建立上游关系
git push -u origin recursion-practice
# 后续在该分支上产生新提交后,常见的默认配置下可以直接推送
git push把功能分支合并到 main
先在 recursion-practice 上完成暂存和提交,再把它合并到 main。下面假设远端已有 main,工作区已清理好,仓库允许直接推送主分支。
git merge recursion-practice 把指定分支合入当前分支,所以必须先切换到 main。如果 main 是功能分支的祖先,Git 可以快进合并,直接将 main 移到新的提交,不必额外产生合并提交。
如果拉取失败或合并出现冲突,应先处理报错和冲突,再继续推送。要求 Pull Request 的仓库,则在推送功能分支后创建 PR,由仓库的审查流程完成合并。参见 git merge 官方文档。
# 当前在 recursion-practice 分支,先提交本次修改
git add recursion_sum.py
git diff --cached
git commit -m "Handle negative input in recursion sum"
# 更新本地主分支,再合并功能分支
git switch main
git pull --ff-only origin main
git merge recursion-practice
git status --short --branch
# 将合并结果同步到远端 main
git push origin main推送 main 后功能分支仍可能显示 ahead
本地两个分支可能已经指向同一个新提交,但远端两个分支仍然位于不同位置。例如:
| 分支或引用 | 位置 |
|---|---|
本地 main | 新提交 |
本地 recursion-practice | 同一个新提交 |
origin/main | 新提交 |
origin/recursion-practice | 之前的提交 |
这是因为 git push origin main 更新的是远端 main。功能分支仍显示领先其上游一个提交,不影响主分支已经包含改动。是否继续同步或删除功能分支,取决于后续是否还使用它。
判断合并是否完成,应检查目标分支的提交关系和实际推送结果,并确认未提交修改是否仍留在工作区;不要只根据功能分支的 ahead 提示推断合并失败。
常见提示与操作前的检查
| 命令或提示 | 应怎样理解 |
|---|---|
git pushpush | 命令拼写错误,应为 git push。 |
git status --shotr | 选项拼写错误,应为 --short,或使用 -s。 |
has no upstream branch | 当前分支尚未配置上游;首次推送时明确指定远端与分支。 |
Everything up-to-date | 推送目标无需更新;仍需用 git status 判断是否有未提交修改。 |
提交前确认 git diff --cached 中确实出现了准备提交的代码;推送前确认分支和远端;合并前确认当前位于目标分支。这三个检查分别对应“提交什么”“推到哪里”和“合入哪里”。
进一步阅读本站的 递归与逐层返回、Python 程序入口与 help(),可以把函数内部的执行过程与脚本入口连接起来。