使用 TortoiseGit 更新项目时,有时远端信息已经获取成功,文件却没有更新,最后还提示退出码 1。一个常见原因是:本地文件发生了变化,Git 为了避免覆盖这些内容,停止了合并。
本文记录一次实际处理过程,并整理出适合日常使用的 TortoiseGit 操作步骤。示例中的路径和项目名称均已泛化。
一、现象:远端显示 up to date,Pull 仍然失败
下面是脱敏后的典型报错:
git.exe pull --progress -v --no-rebase -- "origin"
From https://example.com/team/project
= [up to date] development -> origin/development
error: Your local changes to the following files would be overwritten by merge:
.gitignore
firmware/sdkconfig
src/main.c
tools/publish/device.bin
Please commit your changes or stash them before you merge.
error: The following untracked working tree files would be removed by merge:
tools/modules/device_info/__init__.py
tools/modules/device_info/client.py
Please move or remove them before you merge.
Aborting
关键在两处:
| 提示 | 含义 |
|---|---|
local changes ... would be overwritten by merge |
本地已有修改,更新可能覆盖它们 |
untracked working tree files ... would be removed by merge |
存在尚未被 Git 跟踪的新文件,更新可能移除它们 |
这里的 [up to date] 表示本次获取没有改变相应的远端跟踪引用,不代表本地工作目录已经更新。Pull 包含获取和合并两个阶段;前一个阶段成功,后一个阶段仍可能失败。
因此,这类报错通常不需要反复登录或重新 Clone,首先应检查本地修改。
二、为什么我没有主动改代码,也会出现本地修改?
打开项目、编译固件或运行工具,也可能改变仓库里的文件。例如:
- IDE 保存了本机工具路径、串口号等设置。
- 构建过程更新了
sdkconfig、依赖锁文件或受管理组件。 - 编译结果覆盖了仓库中已经跟踪的
.bin文件。 - Python 程序产生了
__pycache__和.pyc文件。 - 手动解压、复制文件,把不同版本的内容放进了同一个工作目录。
但不能据此认定所有修改都是可以丢弃的。源码、协议文件和文档的变化也可能是有价值的工作,应先查看差异或备份。
三、推荐处理方式:先储藏,再拉取
目标是:保留本地修改,让工作目录更新为远端最新版,同时不向远端提交任何本地文件。
第 1 步:停止正在修改项目文件的程序
先结束正在进行的编译,并关闭会生成或写入文件的项目工具。这样可以避免储藏或拉取期间,工作目录又产生新变化。
第 2 步:检查本地修改
在项目根目录空白处右键,选择:
TortoiseGit → Check for modifications(检查修改)
查看修改、删除和未版本控制的文件;双击已修改文件可以查看差异。Windows 11 如果没有显示菜单,先点击“显示更多选项”。
第 3 步:储藏本地修改,并包含未跟踪文件
右键选择:
TortoiseGit → Stash changes(储藏更改)
填写容易识别的备注,例如:
更新前备份-20261022
勾选 Include untracked(包含未跟踪文件),然后点击确定。
这一步很关键:默认储藏主要处理已经跟踪的文件。对于报错中的新文件,需要启用 Include untracked 才能一起保存。
普通情况下不需要勾选 --all。它还会包含被忽略的文件,可能把大量构建目录和依赖也纳入储藏。
储藏是本机 Git 仓库中的备份操作,不需要 Commit 或 Push 到远端。完成后,通过 Stash List(储藏列表) 确认备份存在。
第 4 步:确认工作目录可以更新
再次打开“检查修改”,确认阻止更新的修改和未跟踪文件已经处理完毕。
如果储藏报错,或工作目录仍有需要保留的改动,先处理这些问题,再继续拉取。不要把“尝试过储藏”当作“备份已经成功”。
第 5 步:执行 Pull
右键选择:
TortoiseGit → Pull(拉取)
确认远端是 origin,远程分支是自己需要更新的分支,例如 development。如果只是跟随远端更新,且没有独立的本地提交,可以勾选 Fast Forward Only(仅快进)。
点击确定,等待成功。如果仅快进被拒绝,说明还存在分支历史方面的问题,需要进一步检查,不应直接改用强制覆盖。
第 6 步:验证更新结果
更新后检查:
- Pull 是否明确显示成功。
- Show log(显示日志)中的当前提交是否是目标分支的最新提交。
- Check for modifications 中是否还有未处理的变化。
文件夹图标可以作为提示,但不要只依赖图标判断是否同步。
四、拉取成功后,需要马上 Stash Pop 吗?
取决于目的。
| 目的 | 建议 |
|---|---|
| 只想使用远端最新版 | 保留储藏,暂时不恢复 |
| 需要继续之前的本地开发 | 先检查储藏内容,再恢复需要的修改 |
如果立刻 Stash Pop,之前的改动就会重新进入工作目录。本地文件可能再次与远端不同,下次 Pull 时仍可能遇到相同问题。
需要恢复时,可以从 Stash List 选择相应记录并使用 Apply(应用)。Apply 会保留储藏记录;Pop(弹出) 则在应用成功后移除对应记录。恢复时如果新旧内容不兼容,还需要解决冲突。
储藏保存在当前仓库的 .git 中,并不是独立的异地备份。删除仓库或清空储藏前,应另行保存仍然需要的内容。
五、习惯命令行时,对应的操作是什么?
在项目根目录的终端中执行,分支名请替换为实际分支:
# 查看当前分支和本地变化
git status --short --branch
# 储藏已跟踪文件的修改,并包含未跟踪文件
git stash push --include-untracked -m "更新前备份-20261022"
# 确认备份存在,再次检查工作目录
git stash list
git status --short --branch
# 仅允许快进,更新指定分支
git pull --ff-only origin development
# 获取当前提交,并检查与远端分支是否有差异
git rev-parse HEAD
git rev-list --left-right --count HEAD...origin/development
git diff --exit-code origin/development
git status --short --branch
这些命令需要逐步检查结果;如果储藏失败,应停止后续步骤。rev-list 显示 0 0 表示两个提交之间互不领先、互不落后;git diff 无输出且退出码为 0,表示受跟踪文件与该远端版本没有差异。
以上流程没有 Push。为保持更新后的工作目录与远端一致,也没有自动恢复储藏。
六、这次还遇到了两个额外问题
1. Git 索引文件缺失
实际检查时发现:Git 索引文件缺失,导致 12,127 个原有文件显示为已暂存删除,而磁盘上仍存在这些文件。
这与普通的“本地修改将被覆盖”不是同一个问题。处理时先记录仓库状态,确认索引确实缺失、没有其他 Git 操作正在进行,再根据当前提交重建索引,保留工作目录内容,随后储藏实际修改。
重建后识别出 70 个受跟踪文件的修改或删除,以及 42 个未跟踪文件,均已备份。不能仅凭大量删除提示,就假定源码真的丢失了。
不要把删除 .git/index、清空索引或批量取消版本控制当作日常解决办法。索引异常需要单独诊断,重建还会改变暂存状态。
2. Windows 文件路径过长
储藏时还遇到了:
Filename too long
Cannot save the current worktree state
这个项目的依赖目录嵌套较深,在当前仓库启用 Git 长路径支持后,储藏成功:
git config --local core.longpaths true
这是当前仓库的本地设置,不修改远端。它可以帮助 Git 处理长路径,但并不保证 IDE、压缩软件或其他构建工具都支持同样长度的路径。
新项目也可以选择更短的目录,例如 D:\Git\project,减少路径嵌套。
七、怎样减少再次发生?
1. 开始工作前先更新
尽量采用下面的顺序:
检查修改 → 必要时储藏 → Pull → 编译、测试或修改
这样能减少本地已经发生大量变化后,再合并远端更新的情况。
2. 分开维护“更新用目录”和“开发测试目录”
对于主要负责获取固件、编译和验证的使用者,可以 Clone 两份独立的工作目录:
D:\Git\project-latest 用于获取和查看远端版本
D:\Git\project-test 用于修改、编译和测试
更新用目录尽量保持干净,避免在那里执行会改写配置和发布文件的程序。开发测试目录仍可能产生修改,需要按正常 Git 流程管理。
3. 由维护者统一调整生成文件的管理方式
长期来看,应区分源码、配置模板、本机配置、构建缓存和发布产物。例如:
- 本机路径、串口号等设置采用本地配置或模板。
- 缓存和临时构建文件纳入合理的忽略规则。
- 构建输出在项目支持的情况下放到独立目录。
- 如果需要跟踪发布用
.bin,考虑避免日常编译直接覆盖它们。
这些是项目层面的改进,需要结合团队发布方式决定,不能简单地把所有配置或固件文件都取消跟踪。
4. 理解 .gitignore 的限制
.gitignore 不会停止跟踪已经被 Git 跟踪的文件。
因此,已经进入版本管理的 sdkconfig、.bin 或 .pyc 文件,即使加入忽略规则,后续变化仍可能显示为本地修改。
是否取消跟踪,需要由维护者评估并提交到仓库。不要为了让状态看起来干净,就批量取消项目文件的跟踪。
八、常见问题速查
| 问题 | 处理建议 |
|---|---|
| 远端显示 up to date,但 Pull 失败 | 查看后面的合并报错,不能只看获取阶段 |
| 提示本地修改会被覆盖 | 先检查差异,再储藏需要保留的修改 |
| 提示未跟踪文件会被移除或覆盖 | 储藏时勾选 Include untracked,或另行备份并移出冲突路径 |
| 不想向远端提交自己的文件 | 使用本地 Stash,不执行 Push |
| 更新后只想使用最新版 | 暂不恢复旧储藏 |
| 加入 .gitignore 后仍显示修改 | 确认文件是否早已被 Git 跟踪 |
| 显示成千上万个文件被删除,但文件还在 | 检查索引状态,先备份并诊断,避免直接强制操作 |
| 储藏时报 Filename too long | 检查长路径支持和项目所在目录的长度 |
这次处理的结果是:本地分支成功快进到远端最新提交,工作目录干净,原有修改仍保存在本机储藏中,没有向远端提交或推送本地文件。这里只验证了版本同步,没有重新编译或烧录。