背景
在windows上面的git项目 copy到了mac 然后又copy到了windows
几经周折后出现了一些难以预料的情况
前言
在多人协作的 Android 项目中,我们在 dev 和 main 分支之间切换是常态。但最近遇到一个棘手的问题:从 dev 切到 main 后,想再切回 dev 时被卡死。无论怎么 Reset,Git 总提示有文件未提交,且子模块(Submodule)一直显示 untracked content。
本文记录了这次问题的完整排查过程和解决方案,主要涉及 Git 强制切换、环境差异(CRLF/Filemode)以及子模块的清理。
1. 问题现场
我在 main 分支执行 git status,显示如下状态:
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: gradle.properties
modified: module_src/.../lzqtestActivity.kt
modified: lib_core (untracked content) <-- 子模块异常
modified: sh (untracked content) <-- 子模块异常
no changes added to commit
我想切回 dev,Git 报错拦截:
$ git checkout dev
error: Your local changes to the following files would be overwritten by checkout:
gradle.properties
...
Please commit your changes or stash them before you switch branches.
Aborting
2. 尝试常规修复(失败)
既然我不需要 main 分支上的任何修改,第一反应是 重置(Reset):
git reset --hard HEAD
诡异的现象发生了:重置命令执行成功了,但再次 git status,那些文件依然显示 modified!文件内容明明没有变化(只是 Hash 变了),但 Git 就是认为它变了。
此外,尝试更新子模块时还报了一个配置错误:
fatal: No url found for submodule path 'Android-skin-support' in .gitmodules
这是一个非常棒的实战案例,涵盖了 Git 分支切换僵局 、Windows/Linux 环境差异导致的"假"修改 、以及 Submodule(子模块)的幽灵状态。
我为你整理了一篇技术博客草稿,你可以直接复制到你的博客(如 CSDN、掘金、个人博客)中,或者根据需要稍作润色。
【Git实战】记录一次分支切换失败的排查:Submodule 脏状态与强制切换
前言
在多人协作的 Android 项目中,我们在 dev 和 main 分支之间切换是常态。但最近遇到一个棘手的问题:从 dev 切到 main 后,想再切回 dev 时被卡死。无论怎么 Reset,Git 总提示有文件未提交,且子模块(Submodule)一直显示 untracked content。
本文记录了这次问题的完整排查过程和解决方案,主要涉及 Git 强制切换、环境差异(CRLF/Filemode)以及子模块的清理。
1. 问题现场
我在 main 分支执行 git status,显示如下状态:
codeBash
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: gradle.properties
modified: module_src/.../lzqtestActivity.kt
modified: lib_core (untracked content) <-- 子模块异常
modified: sh (untracked content) <-- 子模块异常
no changes added to commit
我想切回 dev,Git 报错拦截:
codeBash
$ git checkout dev
error: Your local changes to the following files would be overwritten by checkout:
gradle.properties
...
Please commit your changes or stash them before you switch branches.
Aborting
2. 尝试常规修复(失败)
既然我不需要 main 分支上的任何修改,第一反应是 重置(Reset):
codeBash
git reset --hard HEAD
诡异的现象发生了:重置命令执行成功了,但再次 git status,那些文件依然显示 modified!文件内容明明没有变化(只是 Hash 变了),但 Git 就是认为它变了。
此外,尝试更新子模块时还报了一个配置错误:
codeBash
这说明 main 分支的 .gitmodules 配置可能已经过时或损坏。
3. 原因分析
经过排查,导致"重置无效"且无法切换分支的原因主要有两点:
-
环境差异(CRLF vs LF / Filemode) :
在 Windows 下开发 Android,如果 Git 配置没处理好换行符(CRLF),或者文件权限(Filemode)发生变化,Git 会认为文件被修改了。即使你 reset --hard,文件系统写入时可能又带上了 Windows 的特性,导致 Git 依然认为文件是"脏"的。
-
子模块的 Untracked Content :
lib_core (untracked content) 表示子模块目录下有未追踪的文件(通常是编译生成的 build/ 目录或缓存)。Git 主仓库检测到子模块文件夹里"不干净",因此标记为 modified。
4. 解决方案
步骤一:强制切换分支 (Force Checkout)
既然确认本地 main 分支的修改全是干扰项,不需要保留,直接使用 -f 参数强制切回目标分支。这比 reset 更管用,因为它会强制重写工作区。
git checkout -f dev
步骤二:清理子模块的"幽灵"状态
切回 dev 后,git status 依然提示:
modified: lib_core (untracked content)
modified: sh (untracked content)
这是因为子模块里残留了编译垃圾文件。我们需要进入子模块目录进行深度清理:
# 清理 lib_core
cd lib_core
git clean -fd # 强制删除未追踪的文件和目录
git checkout . # 撤销对被追踪文件的修改
cd ..
# 清理 sh
cd sh
git clean -fd
git checkout .
cd ..
注意:git clean -fd 会永久删除未加入版本控制的文件,请确保里面没有重要的新建代码。
步骤三:彻底同步
最后,确保 dev 分支的子模块配置是最新的:
git pull origin dev
git submodule update --init --recursive
5. 避坑指南(一劳永逸)
为了防止下次编译后子模块又变红(因为生成了 build 文件),可以配置 Git 忽略子模块的脏状态。
在主项目根目录下执行:
# 忽略子模块内部的 untracked content
git config submodule.lib_core.ignore dirty
git config submodule.sh.ignore dirty
果是 Windows 用户,建议同时检查换行符和权限配置,避免文件"假修改":
git config --global core.filemode false
git config --global core.autocrlf true
总结
当 git reset --hard 失效,且确定不需要保留本地修改时,git checkout -f <branch> 是最快的脱困方法。对于子模块的 untracked content,如果不通过 git clean 清理,Git 会一直认为主仓库有变动,善用 ignore dirty 配置可以减少很多干扰。
出现这个的原因是windows 和mac 的 .DS_Store文件导致
cd ..
Updated 0 paths from the index
Removing .DS_Store
Removing src/main/assets/.DS_Store
Removing src/main/res/.DS_Store
Removing src/main/res/drawable-night-xxhdpi/.DS_Store
Removing src/main/res/drawable-xxhdpi/.DS_Store
PS D:\project\android\jinhui\ht-rn\android> ^C
PS D:\project\android\jinhui\ht-rn\android>
解决方案
方案一:全局忽略(最推荐,一劳永逸)
echo .DS_Store > $env:USERPROFILE\.gitignore_global
2. 告诉 Git 使用这个文件作为全局规则
git config --global core.excludesfile "$env:USERPROFILE\.gitignore_global"
果:
从此以后,无论是在父目录,还是在 app、lib_core 等任何子模块里,Git 都会自动无视 .DS_Store 文件,你再也不用操心它了。
方案二:仅在当前父项目配置(你的原需求)
如果你一定只想改当前项目的父目录 .gitignore,写法如下,但要记得这无法自动覆盖子模块(除非子模块里没有 .gitignore 且层级结构允许,但通常不可靠)。
1. 修改父目录 .gitignore
打开主目录下的 .gitignore 文件,在最后加上这两行:
# 忽略所有目录下的 .DS_Store
.DS_Store
**/.DS_Store
注:** 表示递归匹配任意层级的子目录。
2. (关键)解决子模块问题
正如前面所说,父目录的规则管不到 app 子模块。如果不想用方案一,你就必须去修改 app 子模块里的 .gitignore,但这会导致 app 产生一个新的提交(Change),这通常是不建议的(为了一个本地垃圾文件去改动代码库)。
所以,请务必使用方案一。
补充:清理残留的"僵尸"文件
配置好忽略规则后,Git 只会忽略 "新出现的" 文件。如果某些 .DS_Store 已经被 Git 追踪(Tracked)或者残留在缓存区,你需要彻底清理一次:
# 1. 确保在主目录
# 2. 删除所有 .DS_Store 文件(Windows下)
Get-ChildItem -Recurse -Include .DS_Store | Remove-Item -Force
# 3. (如果有被意外提交的) 从 Git 索引中移除
git rm --cached -r .DS_Store 2>$null