---
title: "git 问题记录"
canonical_url: "https://wwlight.vercel.app/guides/notes/git-qa"
---
# git 问题记录

## 文件名大小写修改不生效

<note>

场景：在 macOS / Windows 上把 `demo.js` 重命名为 `Demo.js`，`git status` 却没有变化；或跨平台协作时，远程仓库里同时存在仅大小写不同的两个文件。

</note>

> Git 在 macOS / Windows 上默认对文件名**大小写不敏感**，直接改文件名往往不会被识别为变更。

<steps level="4">

#### 开启大小写敏感（建议只在当前仓库设置，避免影响其他项目）

```sh
$ git config --local core.ignorecase false

# 查看当前配置（false 表示敏感）
$ git config --get core.ignorecase
```

#### 用 `git mv` 两步重命名（以 `demo` → `Demo` 为例）

```sh
$ git mv demo demo.tmp   # 先改为临时名
$ git mv demo.tmp Demo   # 再改为目标名
```

#### 若目标大小写版本**已经存在于远程**（历史遗留），需先清除旧索引再提交

```sh
$ git rm --cached demo   # 移除旧名称的缓存记录
$ git add Demo
$ git commit -m "fix: rename demo to Demo"
```

</steps>

## 强制与远程仓库保持一致

<note>

场景：本地历史与远程分叉（误操作 `reset`、`rebase` 失败、或本地有大量不需要的提交），`git pull` 持续冲突，需要**完全丢弃本地改动**，以远程 `main` 为准。

</note>

```bash
$ git fetch --all
$ git reset --hard origin/main   # 将本地 main 重置为远程最新状态
```

## 重新关联本地项目到远程仓库

<note>

场景：误删了项目目录下的 `.git` 文件夹，工作区代码还在，但仓库失去了 Git 历史与远程关联。

</note>

<steps level="4">

#### 在现有代码目录重新初始化，并关联远程地址

```sh
$ git init
$ git remote add origin <origin-url>
$ git fetch
```

#### 以远程 `main` 为准建立本地分支（会覆盖本地与远程不一致的内容）

```sh
$ git checkout -B main origin/main
# 等价写法：
# git reset --hard origin/main
# git branch --set-upstream-to=origin/main main
```

</steps>

## 关闭 Git SSL 证书验证

<note>

场景：通过 HTTPS 访问自建 Git 服务器时，出现 `OpenSSL SSL_read: Connection was reset` 或证书不受信任等错误。常见原因是服务器使用了自签名证书，未被系统或 Git 信任。

</note>

<caution>

关闭证书验证会降低传输安全性，**仅建议在受信任的内网环境临时使用**。生产环境应正确配置证书，而非关闭验证。

</caution>

```sh
$ git config --global http.sslVerify false
```

## 误操作回滚后如何恢复

<note>

场景：执行了 `git reset --hard` 或其他回退操作，发现删错了提交，需要找回之前的代码。

</note>

### 改动已经 commit

```sh
# 查看所有 HEAD 变动记录（含已被 reset 的提交）
$ git reflog

# 恢复到目标 commit（将 <commit> 替换为 reflog 中的 hash）
$ git reset --hard <commit>
```

### 改动尚未 commit

本地工作区文件被覆盖或未保存时，可借助编辑器的历史记录功能恢复，例如 VS Code 的 **Timeline** 面板查看文件本地历史版本。

## pull / push 冲突

<note>

场景：本地有提交、远程也有新提交，直接 `git push` 被拒绝；或 `git pull` 产生多余 merge commit，希望保持提交历史线性。

</note>

```sh
# 先拉取远程变更，并将本地提交变基到远程之上
$ git pull --rebase

# 若有冲突：手动解决后
$ git add .
$ git rebase --continue

# 若要放弃本次 rebase，回到操作前状态
$ git rebase --abort
```

## git merge 时避免产生合并节点

<note>

场景：`feature` 与 `test` 两条并行分支，需要将 `feature` 的改动合入 `test`，且保持历史为一条直线（不产生 merge commit）。

</note>

<steps level="4">

#### 切换到 `feature` 分支，将其变基到 `test` 之上

```sh
$ git checkout feature
$ git rebase test
```

- 若出现冲突：解决冲突后执行 `git rebase --continue`
- 若要放弃变基：执行 `git rebase --abort`

#### 切回 `test` 分支，快进合并 `feature`

```sh
$ git checkout test
$ git merge feature
```

此时 `feature` 已在 `test` 之上，`merge` 为快进（fast-forward），不会生成合并节点。

</steps>

## 合并代码时不覆盖本分支的配置文件

<note>

场景：`test` 和 `feature` 各自维护 `config.js`。要把 `test` 的代码合入 `feature`，但 `config.js` 仍用 `feature` 的版本。普通 `git merge test` 会把 `test` 的 `config.js` 一并带过来。

</note>

<steps level="4">

#### 切到 `feature`，记下合并前的位置

```sh
$ git checkout feature
$ BEFORE_MERGE=$(git rev-parse HEAD)
```

#### 合并 `test`，先不要提交

```sh
$ git merge test --no-commit --no-ff
```

`--no-commit` 停在提交前。快进时没有提交可停，加上 `--no-ff` 才会生成 merge commit 并停住。

#### 把 `config.js` 恢复成 `feature` 的版本

```sh
$ git checkout $BEFORE_MERGE -- config.js
```

合并尚未提交，这条命令把暂存区里的 `config.js` 换回 `feature` 原版。

#### 完成这一次合并提交

```sh
$ git commit --no-edit
```

`--no-edit` 沿用默认的合并说明。暂存区里恢复过的 `config.js` 会和其余文件一起写入这个 merge commit。

</steps>

## git submodule 使用

<note>

场景：父仓库只记下子模块的某一次提交，`<path>` 里的文件来自这次提交。目录里的文件和这次提交不一致时，先看是哪一边落后。

</note>

### 目录还停在旧文件

父仓库已经记下新提交，把 `<path>` 里的文件换成这次提交。

<steps level="4" className="border-s-0">

#### 克隆后 `<path>` 是空目录

普通 `git clone` 会留下空目录。克隆时一并检出：

```sh
$ git clone --recurse-submodules <url>
```

已经克隆过、目录仍是空的：

```sh
$ git submodule update --init --recursive
```

#### `git pull` 之后，`<path>` 里仍是旧文件

父仓库已经指向新提交，子模块目录还没换成这次提交对应的文件。

```sh
$ git submodule update
```

</steps>

### 父仓库还记着旧提交

目录里已经是新内容，让父仓库改记这次提交。

<steps level="4" className="border-s-0">

#### 只改了文件，提交不进去

还没在子模块里形成新提交时，父仓库记下的提交不变。`git status` 会显示 `<path>` 有修改，`git add <path>` 之后没有可提交的内容。

改动不需要保留：

```sh
$ git -C <path> restore .
```

改动需要发布：先在子模块内提交，再让父仓库记下这次新提交。

```sh
$ git -C <path> switch <branch>
$ git -C <path> add -A
$ git -C <path> commit -m "chore: update submodule files"
$ git add <path>
$ git commit -m "chore: update submodule pointer"
```

#### 子模块里已有新提交

新提交已经在子模块目录里。留在父仓库当前分支，把这次提交记下来：

```sh
$ git add <path>
$ git commit -m "chore: update submodule pointer"
```

#### 换成远程分支上的最新提交

先拉取再记。未写 `branch` 时，用远程仓库的默认分支。

```sh
$ git submodule update --remote <path>
$ git add <path>
$ git commit -m "chore: update submodule pointer"
```

</steps>

### 添加子模块

<steps level="4" className="border-s-0">

#### 挂上一个子模块

`git submodule add` 会把 `<path>` 登记成子模块，并克隆到这个目录。不写 `-b` 时，父仓库记下添加那一刻远程默认分支指向的提交。

```sh
$ git submodule add <url> <path>
$ git commit -m "chore: add submodule"
```

#### 让以后的 `update --remote` 跟随某条分支

`-b <branch>` 会把这条分支写进 `.gitmodules`。

```sh
$ git submodule add -b <branch> <url> <path>
```

#### `<path>` 已经在父仓库里

先从父仓库移出这个目录，再添加。`git rm -r` 会删掉工作区里的该目录。

```sh
$ git rm -r <path>
$ git submodule add <url> <path>
```

</steps>


## Sitemap

See the full [sitemap](https://wwlight.vercel.app/sitemap.md) for all pages.
