张宁的个人博客
莫道君行早,更有早行人。
好好上网
业余无线电
效率提升
软硬件知识
新能源汽车
S1:短波频率
关于
张宁的个人博客
上一篇
下一篇
Git 主线与分支详解:切换分支时,本地文件究竟发生了什么?
张宁的个人博客
2026-01-30
阅读 6
软件&硬件
> 创建 Git 分支后,修改的还是原来的项目文件吗?如果修改失败又没有提交,怎样恢复主线版本?Git 是否在切换分支时自动备份了一份文件?本文使用 **NuliBlog** 作为示例,解释分支、工作区、Commit 与文件恢复的实际关系。 ## 一、先记住最重要的结论 **Git 的分支通常不是复制出来的另一套项目文件夹。** 在一个普通 Git 工作目录中,多个分支共用同一个工作区;切换分支时,Git 根据目标分支对应的提交更新工作区中的文件。 - **Commit(提交)**:保存项目在某个时刻的版本快照与历史关系。 - **Branch(分支)**:指向某个提交的引用;不同分支可以沿不同方向发展。 - **HEAD**:标识当前检出位置;通常指向当前分支。 - **Working Tree(工作区)**:本地正在编辑、编译或运行的实际文件。 - **Staging Area / Index(暂存区)**:决定下一次提交包含哪些改动的区域。 **分支切换不是备份操作,未提交修改也不一定属于某个分支。** 判断当前状态时应先执行 `git status`。 ## 二、创建分支时,NuliBlog 文件夹会发生什么? 假设项目的本地目录是: ``` text D:\Projects\NuliBlog\ ├── index.php ├── style.css ├── post.php ├── header.php └── .git\ ``` 其中,`NuliBlog` 存放实际源文件;隐藏目录 `.git` 存放 Git 对象、提交历史和分支等管理信息。 当前 main 主线已经提交了一个稳定版本 A: ``` text main → Commit A 工作区 NuliBlog: index.php (A 版本) style.css (A 版本) post.php (A 版本) ``` 现在执行: ``` bash git switch -c feature/article-toc ``` 这条命令分为四部分: | 片段 | 含义 | | --- | --- | | `git` | 调用 Git 程序 | | `switch` | 切换分支的子命令 | | `-c` | 创建一个分支,并切换过去(create) | | `feature/article-toc` | 新分支名称,`feature/` 只是命名约定 | **Git 不会再创建一个 NuliBlog 文件夹。** 新分支最初和 main 指向同一个提交: ``` text main → Commit A feature/article-toc → Commit A ← HEAD 本地依然只有一个 NuliBlog 工作区。 ``` 因为此时两个分支对应的文件内容一样,刚创建并切换分支时,通常也看不到文件内容变化。 ## 三、在功能分支修改代码,实际修改的是哪里? 假设现在使用编辑器修改: ``` text D:\Projects\NuliBlog\style.css D:\Projects\NuliBlog\post.php ``` 例如,将文章目录宽度调整为 250px,并修改目录折叠效果。 修改之后、还没有 Commit 时: ``` text main → Commit A feature/article-toc → Commit A ← HEAD NuliBlog 工作区: index.php (未改动) style.css (已修改,未提交) post.php (已修改,未提交) ``` **这里非常关键:** 在 Git 中,两个分支指针此时仍然指向 Commit A。刚才修改的内容只存在于当前工作区,还没有形成新的提交。 可以通过以下命令查看: ``` bash git status ``` 再使用: ``` bash git diff ``` 检查尚未暂存的具体代码差异。 ## 四、修改有问题且不想提交,怎么恢复? ### 情况 1:修改了文件,但还没有执行 `git add` 先确认状态: ``` bash git status ``` 如果确定要丢弃**当前目录及子目录下已跟踪文件**在工作区中的未暂存修改,可以执行: ``` bash git restore . ``` 逐项解释: | 片段 | 含义 | | --- | --- | | `git` | 调用 Git 程序 | | `restore` | 恢复文件内容的子命令 | | `.` | 当前目录,通常包含其子目录 | 这条命令默认将工作区中的**已跟踪文件**恢复为暂存区的内容。若此前没有暂存任何修改,暂存区通常就是当前 HEAD 提交对应的版本。 **注意:此操作会丢弃相应的未暂存修改,不能简单地通过 Git 找回;它不会自动删除未跟踪的新文件。** 完成后可检查: ``` bash git status ``` 确认工作区干净后再切回 main: ``` bash git switch main ``` ### 情况 2:已经执行了 `git add`,但还没有 Commit `git add` 是把改动放入**暂存区**,并不是保存成 Commit。 如果希望丢弃当前目录下已跟踪文件的所有未提交改动,可以依次执行: ``` bash git restore --staged . git restore . ``` 第一条把改动从暂存区移出,第二条将工作区内容恢复为暂存区当前状态。 也可以明确指定以当前提交 `HEAD` 为来源,同时恢复暂存区和工作区: ``` bash git restore --source=HEAD --staged --worktree . ``` | 片段 | 含义 | | --- | --- | | `restore` | 恢复文件内容或暂存状态 | | `--source=HEAD` | 使用当前 HEAD 对应的提交作为恢复来源 | | `--staged` | 恢复暂存区 | | `--worktree` | 恢复工作区 | | `.` | 作用于当前目录及其子目录 | > **操作警告:** 以上恢复命令会丢弃相应的未提交修改。执行前建议先检查 `git status` 和 `git diff`,重要文件可先复制备份。尤其注意未跟踪的新文件不受上述 `git restore` 命令管理。 ### 情况 3:还不确定是否放弃修改 更稳妥的办法是临时保存改动: ``` bash git stash push -u -m "暂存文章目录修改" ``` | 片段 | 含义 | | --- | --- | | `stash` | 临时存放尚未提交的改动 | | `push` | 把当前改动保存到 stash 列表中 | | `-u` | 同时包含未跟踪文件(untracked),但不包含被忽略的文件 | | `-m` | 为此次暂存写一条说明(message) | | `"暂存文章目录修改"` | 用户填写的说明 | 之后可切换主线: ``` bash git switch main ``` 如果还想继续原来的功能开发: ``` bash git switch feature/article-toc git stash pop ``` `stash pop` 会尝试恢复暂存的改动,并在成功应用后移除相应的 stash 记录;如果发生冲突,需要先解决冲突。 > Stash 适合临时放下工作,不适合作为长期备份的唯一手段。重要里程碑仍建议完成 Commit。 ## 五、原来 main 的版本到底保存在哪里? **并不是切换分支时才把 main 的文件临时备份起来。** 在执行 `git commit` 后,对应版本的数据已经保存到本地 Git 仓库中,通常由 `.git` 中的对象数据库和历史记录管理。 假设在功能分支完成修改并提交: ``` bash git add . git commit -m "feat: add article table of contents" ``` 提交之后,分支关系变成: ``` text Commit A ── Commit B ↑ ↑ main feature/article-toc ↑ HEAD ``` 此时: - **main → A**:仍指向原来的稳定版本。 - **feature/article-toc → B**:指向刚刚提交的新版本。 - **同一个 NuliBlog 工作区**:当前显示的是功能分支对应的文件内容。 执行: ``` bash git switch main ``` Git 根据 main 指向的 A 版本更新工作区,于是文件恢复为 A 的内容。 再执行: ``` bash git switch feature/article-toc ``` Git 根据功能分支指向的 B 版本更新工作区,于是文件又变成 B 的内容。 所以,**只要相应的版本已经 Commit,Git 就能够按分支指针找到相应的历史版本;不需要保留两个同名项目文件夹。** ## 六、为什么有时没提交也能切换分支,有时却不能? 这是初学者常见的误区。 假设已修改 `style.css`,但还没提交,直接执行: ``` bash git switch main ``` Git 可能出现不同结果: | 工作区状态 | Git 可能采取的行为 | | --- | --- | | 本地修改不影响目标分支的切换 | 允许切换,并保留未提交修改 | | 切换会覆盖本地修改 | 拒绝切换,提示先提交、暂存或处理修改 | | 工作区干净 | 正常切换,显示目标分支版本 | 因此,**`git switch main` 不是恢复按钮,也不会自动丢弃所有改动**。切换成功后保留下来的未提交修改,有可能继续出现在 main 工作区里。 建议每次切换前执行: ``` bash git status ``` 如果显示有未提交改动,先决定要: - 保留并提交:`git add` + `git commit`; - 临时保留:`git stash`; - 确认不要了:`git restore`(注意丢失风险)。 ## 七、把一次完整的分支开发串起来 下面是常见流程。假设从 main 开始,且工作区干净。 ``` bash # 1. 切换到 main git switch main # 2. 同步远程主线 git pull # 3. 创建并切换到功能分支 git switch -c feature/article-toc # 4. 在 NuliBlog 文件夹中修改代码 # 使用编辑器修改 style.css、post.php 等 # 5. 检查修改 git status git diff # 6. 测试通过后暂存和提交 git add . git commit -m "feat: add article table of contents" # 7. 首次推送功能分支 git push -u origin feature/article-toc ``` 接着,在 GitHub 创建 Pull Request,将 `feature/article-toc` 合并到 `main`。合并完成后,在本地执行: ``` bash git switch main git pull ``` 这时本地 main 就包含了已经合并的新功能。 如果功能开发失败、且不打算保留未提交修改,按前文步骤检查并恢复工作区,然后再切回 main。 ## 八、不同操作对工作区和 Git 历史的影响 | 操作 | NuliBlog 工作区 | Git 历史 / 分支指针 | | --- | --- | --- | | 在 main 上创建 feature 分支 | 通常不变 | 新分支与 main 指向同一提交 | | 在 feature 修改文件,未提交 | 文件实际改变 | 两个分支指针都不变 | | `git add .` | 文件内容不变 | 改动进入暂存区,分支指针不变 | | 在 feature 执行 Commit B | 文件内容通常不变 | feature 指针前进到 B,main 仍指向 A | | 工作区干净时切到 main | 更新为 main 的版本 | HEAD 切到 main | | 工作区干净时切回 feature | 更新为 feature 的版本 | HEAD 切到 feature | | `git restore .` | 已跟踪文件的未暂存修改被丢弃 | 不产生新 Commit | | `git stash push -u` | 相应改动临时收起 | 普通分支指针不前进;stash 保存一份临时记录 | | 将 feature 合并到 main | 工作区反映合并结果 | main 指向整合后的提交历史 | ## 九、一个额外知识:能否让两个分支分别拥有独立文件夹? 可以。Git 提供 **`git worktree`** 功能,能够让同一个仓库的不同分支分别检出到不同的工作目录中。 但对于初学者,建议先掌握普通的单工作区模式:**一个 NuliBlog 目录 + 多个分支 + Commit / Stash / Restore**。以后确实需要同时比较两个版本、同时开发两个功能,再学习 Worktree。 ## 十、总结 理解 Git 分支可以记住四句话: 1. **分支不是简单地复制一套项目目录,而是指向不同提交的引用。** 2. **修改代码时,修改的就是当前 NuliBlog 工作区中的真实文件。** 3. **已经 Commit 的版本保存在 Git 历史中;未提交修改不会因为创建分支就自动获得备份。** 4. **切换分支会尝试更新工作区,但不等于撤销未提交修改。** 对于个人网站或 AI 辅助开发项目,推荐保持 main 稳定,在功能分支中修改和测试。修改满意就 Commit,不确定就 Stash,确定放弃才 Restore;经过测试后再合并到 main。 这样即使一次界面调整或大规模代码修改失败,也更容易恢复到之前可运行的版本。
文章关键词
git
git主线和分支
工作区
版本恢复
发表评论
让交流带来新的灵感
取消回复
友善表达,认真交流