这篇笔记按“遇到的问题”整理 Git 日常操作。命令默认在项目根目录执行;如果涉及已经推送到远端的历史改写,优先用 --force-with-lease,不要直接用 --force

GitHub recovery codes 应该写进笔记里吗?

不要。GitHub recovery codes 等同于备用登录凭证,不应该放在仓库、博客、截图或公开笔记里。

如果 recovery codes 曾经被推送到公开仓库或 GitHub Pages,建议在 GitHub 的 2FA 设置里重新生成 recovery codes,让旧 codes 失效。

怎么把 remote 的 HTTPS 地址换成 SSH?

查看当前 remote:

git remote -v

origin 从 HTTPS 改成 SSH:

git remote set-url origin git@github.com:<yourname>/<repo>.git

再次确认:

git remote -v

Git 的全局身份、提交模板和 SSH key 怎么配置?

身份信息:

git config --global user.name "wqh011128"
git config --global user.email "wqh011128@163.com"

提交信息模板:

cat <<'EOF' > ~/.gitmessage.txt
<type>[<scope>]: <short-summary>

Problem:
<description of the problem being solved>

Solution:
<description of the solution implemented>

Test:
<description of how the change was tested>

JIRA: ISSUE-<Number>
EOF

git config --global commit.template ~/.gitmessage.txt

Windows 上如果经常遇到 CRLF/LF 问题,可以按项目规范决定是否设置:

git config --global core.autocrlf true

生成 SSH key 并查看公钥:

ssh-keygen -t ed25519 -C "wqh011128@163.com"
cat ~/.ssh/id_ed25519.pub

如果环境不支持 ed25519,再考虑 RSA:

ssh-keygen -t rsa -b 2048 -C "wqh011128@163.com"
cat ~/.ssh/id_rsa.pub

git fetchgit pullgit pull --rebase 有什么区别?

git fetch 只更新远端分支信息,不会改动当前工作区和当前分支:

git fetch origin

git pull 等价于先 fetch,再把远端内容整合到当前分支。默认通常是 merge:

git pull origin main

这条命令的意思是:把远端 origin/main 整合到你当前 checkout 的分支里。它不是“切到 main 再拉取”,当前分支是谁就影响谁。

如果想让历史更线性,常用 rebase:

git pull --rebase origin main

概念上可以理解成:

git fetch origin main
git rebase origin/main

git pull --rebase origin dev/vl 会改哪个分支?

会改你当前 checkout 的分支。

例如你当前在 feat 分支,执行:

git pull --rebase origin dev/vl

含义是:

  1. origin 拉取 dev/vl 的最新提交。
  2. 把当前分支 feat 上你自己的提交 rebase 到 origin/dev/vl 之后。

所以它不是“把 dev/vl 拉到 dev/vl”,而是“用 origin/dev/vl 作为当前分支的新基底”。

例子:

origin/dev/vl: A-B-C
local feat:    x-y

执行后大致变成:

A-B-C-x'-y'

怎么复制一个 branch 继续开发?

如果想基于最新 main 开一个新分支:

git switch main
git pull --rebase origin main
git switch -c feat/example

老版本 Git 可以用:

git checkout -b feat/example

怎么查看、添加和删除 remote?

查看远程连接:

git remote -v

添加远程连接:

git remote add origin <remote-url>

删除某个远程连接:

git remote remove origin

修改远程连接:

git remote set-url origin <remote-url>

怎么从本地项目创建 GitHub 仓库并推上去?

如果本地还没有 Git 历史:

cd /path/to/your-project
git init
git add .
git commit -m "Initial commit"

如果本地已经有提交历史,从绑定远端开始即可。

在 GitHub 上创建仓库时,建议创建空仓库:

  • 填仓库名。
  • 先选 Private 或 Public 都可以,按需求决定。
  • 不要勾选 README、.gitignore、license,避免和本地历史冲突。

使用 HTTPS:

git remote add origin https://github.com/<yourname>/<repo>.git
git branch -M main
git push -u origin main

使用 SSH:

git remote add origin git@github.com:<yourname>/<repo>.git
git branch -M main
git push -u origin main

常见坑:

  • HTTPS push 被拒绝或要求登录:GitHub 通常需要 Personal Access Token,不是账号密码。
  • 远端已有 README 导致冲突:可以重建空仓库,或先 git pull --rebase 解决冲突后再 push。
  • 默认分支不是 main:用 git branch -M main 统一。

怎么创建新分支并推送到远端?

先确认当前状态:

git status
git branch --show-current

基于当前分支创建并切到新分支:

git switch -c feat/eau-opt

提交修改:

git add -A
pre-commit run --all-files
git commit -m "Describe your change"

推送新分支:

git push -u origin feat/eau-opt

-u 会建立 upstream。以后在这个分支上直接 git pushgit pull 就行。

当前 feat 分支怎么更新到最新 origin/main

推荐把当前 feature 分支 rebase 到最新 origin/main 上:

git status
git stash push -u -m "wip before rebase"
git fetch origin
git rebase origin/main
git stash pop

如果想让 Git 自动暂存未提交修改,可以用:

git pull --rebase --autostash origin main

注意:这条命令会影响当前 checkout 的分支。先确认自己在目标 feature 分支上。

push 时远程比当前要领先怎么办?

先区分清楚:准备开 PR 前,优先同步的是最新 main,不是盲目 git pull 当前分支。

推荐流程:

git status
git fetch origin
git rebase origin/main

这个习惯很重要。别人从其他分支合 PR 之后,main 可能已经领先;提前 rebase 到最新 main,通常能更早发现冲突,也能避免 PR 里混入过期历史。

如果已经在自己的分支上提交了 commit,再执行:

git rebase origin/main

Git 会把你的 commit 临时拿下来,再放到最新 main 后面重新应用。遇到冲突时按下面处理:

git status
# 手动解决冲突文件
git add <conflict-file>
git rebase --continue

如果发现 rebase 方向不对,直接取消:

git rebase --abort

rebase origin/main 之后,git status 仍然可能显示:

Your branch and 'origin/xxx' have diverged,
and have N and M different commits each, respectively.

这通常不是说你落后于 origin/main,而是说本地分支和远程同名分支 origin/xxx 分叉了。比如之前用同一个分支开过 PR,PR 已经合进 main,但远程功能分支还停在旧位置。

这时不要直接 git pullgit pull 会把 origin/xxx 拉回来整合,可能产生重复提交或无意义 merge。先确认当前分支已经基于最新 main

git log --oneline --graph --decorate --left-right origin/main...HEAD

如果确认当前分支只是 origin/main + 你的新 commit,就更新远程功能分支:

git push --force-with-lease origin xxx

一句话记忆:

rebase origin/main:同步主线。
push --force-with-lease:更新远程功能分支。
不要用 git pull 去解决已经 rebase 到 main 后的同名分支分叉。

Flowra 工作流里 Git 怎么走?

这部分更像项目工具链笔记,主线可以按下面走:

flowra ws myWorkspace
git clone <repo-url>

进入 workspace 后修改代码,然后格式化:

flowra format your_project

再走正常 Git 流程:

git add -A
git commit -m "xxx"
git push

如果需要修改最后一次提交:

git commit --amend
git push --force-with-lease

pre-commit 应该什么时候跑?

安装:

pip install pre-commit

通常在 git add 后、git commit 前跑:

git add -A
pre-commit run --all-files
git commit -m "Describe your change"

如果 hook 自动修了文件,需要重新暂存:

git add -A
git commit -m "Describe your change"

怎么修改过去的 commit message?

如果历史 commit message 写错,并且 CI 或规范检查依赖 commit message,可以用 interactive rebase 的 reword

选择要修改的最近 N 个 commit:

git rebase -i HEAD~N

在编辑器里,把需要修改 message 的那几行从 pick 改成 reword

reword a1b2c3d feat: old message
pick   b2c3d4e fix: another message

保存退出后,Git 会逐个打开 commit message 编辑器。

如果这些 commit 已经推送到远端,修改完成后需要:

git push --force-with-lease

怎么合并过去的多个 commits?

先更新当前分支:

git checkout feat/op_matcher
git fetch origin
git pull --rebase

把最近 6 个提交放进 interactive rebase:

git rebase -i HEAD~6

如果历史里包含 merge commit,并且需要保留 merge 结构,可以改用:

git rebase -i --rebase-merges HEAD~6

编辑器里会看到类似:

pick a1b2c3d feat(op_matcher): reconstruct...
pick b2c3d4e feat(op_matcher): reconstruct...
pick c3d4e5f feat(op_matcher): reconstruct...
pick d4e5f6g feat(op_matcher): support bf16
pick e5f6g7h feat(op_matcher): reconstruct...
pick f6g7h8i feat(op_matcher): optimize compare

如果想把前 3 个合成 1 个,把后两行改成 squashs

pick a1b2c3d feat(op_matcher): reconstruct...
s    b2c3d4e feat(op_matcher): reconstruct...
s    c3d4e5f feat(op_matcher): reconstruct...
pick d4e5f6g feat(op_matcher): support bf16
pick e5f6g7h feat(op_matcher): reconstruct...
pick f6g7h8i feat(op_matcher): optimize compare

保存退出后,Git 会让你编辑新的 commit message。删掉重复内容,保留一个清晰的 summary 和必要 bullet 即可。

如果 rebase 过程中遇到冲突:

git add <conflict-file>
git rebase --continue

如果这些 commit 已经推送过,最后同步远端:

git push --force-with-lease

git commit -c HEAD 是什么?

git commit -c HEAD 会复用当前分支最后一次提交的 message 作为模板,并打开编辑器让你修改,然后创建一个新提交。

基本流程:

git add .
git commit -c HEAD

它和 git commit --amend 的区别:

  • git commit --amend:修改上一次提交,不产生额外的新提交,而是用新 commit 替换旧 commit。
  • git commit -c HEAD:创建一个全新的提交,只是借用上一次提交信息作为草稿。

-c-C 的区别:

  • git commit -c HEAD:复用信息,并打开编辑器供你修改。
  • git commit -C HEAD:直接复用信息,不打开编辑器。

如果想“复制上个提交的消息并改一改,然后发一个新提交”,用 git commit -c HEAD

合并冲突时怎么保留某一边?

如果你明确知道要保留当前分支这一边:

git checkout --ours op_matcher/verify_eau_opcode.py
git add op_matcher/verify_eau_opcode.py

如果你明确知道要保留另一边:

git checkout --theirs op_matcher/verify_eau_opcode.py
git add op_matcher/verify_eau_opcode.py

注意:在 rebase 场景里,ourstheirs 的直觉可能和 merge 场景不同。执行前最好用 git status 和冲突内容确认清楚。

怎么修正最近一次 commit 的 author?

如果最近一次提交的 author 信息不对:

git commit --amend --reset-author --no-edit

如果该提交已经推送过:

git push --force-with-lease

WSL 里 clone 项目失败可以先看什么?

如果是 DNS 或网络解析问题,可以先检查 /etc/resolv.conf

sudo vim /etc/resolv.conf

有些网络环境里,需要把 nameserver 换成本机或可用 DNS 的 IP。这个属于环境问题,不一定是 Git 本身的问题。

amend 之后为什么普通 git push 会让我先 pull?

假设最开始本地和远端都是提交 A

remote: A
local:  A

执行:

git commit --amend

本地提交会从 A 变成一个新的提交,比如 B

remote: A
local:  B

B 不是追加在 A 后面的提交,而是替换了 A。这不是 fast-forward 关系,所以普通 git push 会拒绝,并提示你先 pull。

但这个场景通常不应该先 pull。你真正想做的是用本地新的 B 替换远端旧的 A,因此应该:

git push --force-with-lease

--force--force-with-lease 有什么区别?

--force 会直接强推,不太管远端是否已经被别人更新。

--force-with-lease 会先检查远端是否还是你本地记录里的状态。如果远端在这期间被别人更新过,它会拒绝推送,避免你覆盖别人的提交。

所以平时更推荐:

git push --force-with-lease

怎么退回以前的提交?

如果提交已经推送到远端,最保守的撤销方式是 revert。它会新增一个“反向提交”,保留历史:

git revert HEAD
git push

如果后来决定把“原 commit + revert commit”都从历史中删掉,可以用 interactive rebase:

git rebase -i HEAD~3

在打开的编辑器里,把这两条从:

pick <sha1> feat: original change
pick <sha2> revert: revert original change

改成:

drop <sha1> feat: original change
drop <sha2> revert: revert original change

如果 rebase 过程中状态混乱,先退出:

git rebase --abort

rebase 成功后检查:

git status
git log --oneline -5

如果此时看到类似:

Your branch is behind origin/... by 2 commits

并且确认这 2 个 commit 正是刚刚 drop 掉的那两个,不要 git pull,否则会把它们又拉回来。应该执行:

git push --force-with-lease

完整链路:

git revert HEAD
git push

git rebase -i HEAD~3
# 把原 commit 和 revert commit 改成 drop

git status
git log --oneline -5

git push --force-with-lease

两个本地 clone 同一个分支,一个 force push 后另一个怎么同步?

场景:

  • 本地 1 对 feat/dump_llo 做了 rebase。
  • 本地 1 执行了 git push --force-with-lease
  • 远端 origin/feat/dump_llo 变成一条新的提交历史。
  • 本地 2 仍然保留旧历史。

这时本地 2 执行:

git pull

可能会看到:

+ 815a613...2889e70 feat/dump_llo -> origin/feat/dump_llo  (forced update)
hint: You have divergent branches and need to specify how to reconcile them.
fatal: Need to specify how to reconcile divergent branches.

原因是本地旧历史和远端新历史已经不是 fast-forward 关系。git pull 不知道你想 merge、rebase,还是只接受 fast-forward。

如果本地 2 没有需要保留的提交,最直接的做法是让它完全对齐远端:

git fetch origin
git switch feat/dump_llo
git reset --hard origin/feat/dump_llo

如果担心本地 2 上有内容需要找回,先备份:

git branch backup/feat-dump-llo-local2
git fetch origin
git reset --hard origin/feat/dump_llo

这个场景不推荐直接 git pull,因为它会尝试整合两条不同历史,而你的目标通常只是“本地 2 跟远端同步”。

Git worktree 和 branch 有什么区别?

branch 是 Git 历史上的提交指针,例如 maincodex/reconstruct。它记录代码历史走到哪里。

worktree 是磁盘上的实际工作目录。一个仓库可以有多个 worktree,每个 worktree 都是一份可以编辑、运行、提交代码的工作现场。

可以这样理解:

repo
├─ main worktree -> main
├─ worktree A    -> feature/login
└─ worktree B    -> codex/reconstruct

关键点:

  • 一个 worktree 同一时间通常只能 checkout 一个 branch,或者处于 detached HEAD。
  • 一个 branch 同一时间通常只能被一个 worktree 占用。
  • 如果某个 branch 已经被别的 worktree 使用,新的 worktree 不能直接 git switch 到这个 branch。
  • 删除 Codex 对话不等于删除 Git worktree。worktree 是 Git 记录的本地目录,需要用 Git 命令清理。

worktree 常用命令有哪些?

查看当前有哪些 worktree:

git worktree list

查看某个 worktree 的状态:

git -C "C:/path/to/worktree" status --short --branch

新建一个 worktree,并让它绑定新分支:

git worktree add "C:/path/to/new-worktree" -b feat/example main

新建一个 worktree,并让它 checkout 已有分支:

git worktree add "C:/path/to/new-worktree" feat/example

删除不再使用的普通 worktree:

git worktree remove "C:/path/to/old-worktree"

如果目录已经被手动删掉,但 Git 记录还在:

git worktree prune

查看当前 worktree 绑定的分支:

git branch --show-current

如果显示不出分支名,可能是 detached HEAD:

git status --short --branch

切换分支:

git switch main
git switch codex/reconstruct

worktree 里目标分支被占用怎么办?

先查看 worktree 结构:

git worktree list

可能会看到类似:

E:/my_github_page/wqh011128.github.io                         [codex/reconstruct]
C:/Users/吴启航/.codex/worktrees/4855/wqh011128.github.io     (detached HEAD)

这说明 codex/reconstruct 分支已经被 E:/my_github_page/wqh011128.github.io 这个 worktree 占用,当前 Codex worktree 不能直接接管这个分支。

如果被占用的是主工作目录,不能用下面这条命令删除:

git worktree remove "E:/my_github_page/wqh011128.github.io"

否则会看到:

fatal: 'E:/my_github_page/wqh011128.github.io' is a main working tree

正确思路是让占用分支的 worktree 先切到别的分支,例如 main,从而释放目标分支。

推荐步骤:

git worktree list
git -C "E:/my_github_page/wqh011128.github.io" status --short --branch
git -C "C:/Users/吴启航/.codex/worktrees/4855/wqh011128.github.io" status --short --branch

确认没有未提交内容后,让主工作目录切回 main

git -C "E:/my_github_page/wqh011128.github.io" switch main

然后在当前 worktree 接管目标分支:

git switch codex/reconstruct

最后确认:

git worktree list
git status --short --branch

期望结构类似:

E:/my_github_page/wqh011128.github.io                         [main]
C:/Users/吴启航/.codex/worktrees/4855/wqh011128.github.io     [codex/reconstruct]

worktree 上有本地修改时怎么处理?

如果目标分支或目标 worktree 上有本地修改,先决定是否保留。不要直接切分支、删除 worktree 或 reset。

如果确认不保留已跟踪文件的修改:

git restore --staged --worktree .

如果还存在未跟踪文件或目录,例如 skills/

git clean -fd -- "skills/"

然后再按需要 rebase 到 main

git rebase main

日常使用 worktree 的习惯是什么?

  • 主目录长期放 main
  • 每个 Codex 任务使用单独 worktree 和单独分支。
  • 一个长期开发分支尽量固定在一个 worktree 使用。
  • 用完的 worktree 先检查 git status --short --branch,确认干净后再 git worktree remove
  • 看到 detached HEAD 先不要慌,它只是说明当前 worktree 没有绑定分支;先用 git worktree list 判断目标分支是否被别的 worktree 占用。