git-commit-merge-guide

Git Commit 合并操作指南

场景描述

当需要将当前staged的代码合并到历史中的某个特定commit(而不是最新的commit)时,标准的git commit --amend无法满足需求,因为它只能修改最新的commit。

操作步骤详解

1. 查看当前状态

1
2
3
4
5
# 查看当前staged的文件
git status

# 查看commit历史,确定目标commit
git log --oneline -5

输出示例:

1
2
3
4
5
34e0fcd add click link to avatar          # 最新commit
f42ce7a change author description
af1ae21 add head href link               # 目标commit
bdc4b45 fix hexo-blog-encrypt show giscus comments
fbb3532 add comment giscus

2. 回退到目标commit

首先需要回到包含目标commit的状态:

1
2
# 回退到目标commit
git reset --hard af1ae21

原理: git reset --hard会将HEAD指针移动到指定的commit,并丢弃该commit之后的所有更改。

3. 提取需要合并的文件内容

从之前的commit中提取需要合并的文件内容:

1
2
3
4
5
6
7
# 从之前包含所需代码的commit中提取文件
git show <commit-hash>:<file-path> > <file-path>

# 实际例子
git show 8e298e3:source/css/add_head_prefix.css > source/css/add_head_prefix.css
git show 8e298e3:scripts/add_head_prefix.js > scripts/add_head_prefix.js
git show 8e298e3:_config.butterfly.yml > _config.butterfly.yml

原理: git show <commit>:<file>可以显示指定commit中特定文件的内容,通过重定向可以将其写入到工作目录。

4. 添加文件并修改目标commit

1
2
3
4
5
# 添加恢复的文件到暂存区
git add .

# 修改目标commit,将新文件包含进去
git commit --amend --no-edit

原理: git commit --amend会用当前的暂存区内容重新创建最新的commit,保留原有的commit message(--no-edit)。

5. 重新应用后续的commits

使用cherry-pick重新应用目标commit之后的其他commits:

1
2
3
# 按顺序重新应用后续的commits
git cherry-pick f42ce7a  # change author description
git cherry-pick 34e0fcd  # add click link to avatar

原理: git cherry-pick会将指定的commit应用到当前分支,创建新的commit但保持相同的更改和commit信息。

完整命令序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 1. 查看状态
git status
git log --oneline -5

# 2. 临时保存需要合并的commit hash(如果不在当前分支)
TARGET_COMMIT="af1ae21"
SOURCE_COMMIT="8e298e3"  # 包含要合并代码的commit

# 3. 重置到目标commit
git reset --hard $TARGET_COMMIT

# 4. 从源commit恢复需要的文件
git show $SOURCE_COMMIT:source/css/add_head_prefix.css > source/css/add_head_prefix.css
git show $SOURCE_COMMIT:scripts/add_head_prefix.js > scripts/add_head_prefix.js
git show $SOURCE_COMMIT:_config.butterfly.yml > _config.butterfly.yml

# 5. 添加并修改目标commit
git add .
git commit --amend --no-edit

# 6. 重新应用后续commits
git cherry-pick <next-commit-hash-1>
git cherry-pick <next-commit-hash-2>
# ... 继续应用其他需要的commits

重要注意事项

1. 备份重要数据

1
2
# 在执行重写历史的操作前,建议创建备份
git branch backup-branch

2. 检查冲突

cherry-pick过程中可能会遇到冲突,需要手动解决:

1
2
3
4
5
# 解决冲突后
git add .
git cherry-pick --continue
# 或者取消当前cherry-pick
git cherry-pick --abort

3. 强制推送(如果已推送到远程)

1
2
# ⚠️ 谨慎使用!这会重写远程历史
git push --force-with-lease origin main

替代方案:交互式Rebase

对于更复杂的rebase操作,可以使用交互式rebase:

1
2
3
4
5
# 从目标commit开始交互式rebase
git rebase -i af1ae21~1

# 在编辑器中,将需要合并的commit标记为 'fixup' 或 'squash'
# 保存后,Git会自动合并commits

核心概念解释

Git reset 的三种模式

  • --soft: 只移动HEAD指针,保留暂存区和工作目录
  • --mixed (默认): 移动HEAD指针,清除暂存区,保留工作目录
  • --hard: 移动HEAD指针,清除暂存区和工作目录

Cherry-pick vs Merge

  • Cherry-pick: 选择性地应用单个commit
  • Merge: 合并整个分支的历史

Commit Hash 寻找

1
2
3
4
# 查找包含特定更改的commit
git log --oneline --grep="keyword"
git log --oneline -- <file-path>
git show --name-only HEAD~1  # 查看上一个commit的文件

故障恢复

如果操作过程中出现问题:

1
2
3
4
5
6
7
8
# 查看reflog找回丢失的commits
git reflog

# 恢复到特定状态
git reset --hard HEAD@{2}

# 或者恢复到已知的commit
git reset --hard <known-commit-hash>

最佳实践

  1. 操作前备份: 始终在重写历史前创建分支备份
  2. 小步操作: 一次只做一个更改,便于回滚
  3. 本地测试: 在本地充分测试后再推送到远程
  4. 团队协作: 如果是共享分支,需要与团队沟通后再重写历史
  5. 使用reflog: 熟悉git reflog来找回丢失的commits

这个操作虽然相对复杂,但在需要保持commit历史的整洁性和逻辑性时非常有用。记住核心原理:通过reset回到过去,然后重新构建想要的commit历史。