Git合并前将开发分支的多个commit压缩成一个
|字数总计:2.3k|阅读时长:8分钟|阅读量:|
在日常开发中,我们经常会在自己的功能分支上进行多次提交,这些提交可能包含了很多"修复typo"、"调整格式"等杂乱的中间提交。当准备向上游分支提交PR时,将这些杂乱的commit压缩成一个干净、整洁的commit是一个好习惯。本文将介绍如何使用git reset --soft来实现这一目标。
场景说明
假设我们有以下场景:
- 本地开发分支:
feat-dev
- 上游目标分支:
upstream/develop
- 目标:将
feat-dev分支相对于upstream/develop的所有commit压缩成一个commit
完整操作步骤
第一步:确保代码已提交
在开始操作前,确保当前工作区是干净的:
如果有未提交的更改,请先提交或暂存。
第二步:更新上游分支(重要!)
在压缩提交之前,强烈建议先更新本地的上游分支,以避免后续合并时产生冲突:
1 2 3 4 5
| git fetch upstream
git fetch origin
|
第三步:合并上游分支到开发分支(推荐)
将上游分支的最新代码合并到你的开发分支中,提前解决可能的冲突:
1 2 3 4 5 6 7 8 9
| git checkout feat-dev
git merge upstream/develop
|
第四步:创建备份分支(可选但推荐)
以防万一操作失误,建议先创建一个备份分支:
1
| git branch feat-dev-backup
|
第五步:查看要压缩的提交数量
可以先查看有多少个提交需要被压缩:
1 2 3 4 5 6 7 8 9
| git merge-base feat-dev upstream/develop
git log --oneline 97de0a079038a631719408d1a5c3b0b4db8c593a..feat-dev | wc -l
git log --oneline upstream/develop..feat-dev
|
第六步:执行软重置
使用git reset --soft将HEAD重置到上游分支,但保留所有更改在暂存区:
1
| git reset --soft upstream/develop
|
说明:--soft参数的作用是只移动HEAD指针,不改变暂存区和工作区的内容。这意味着所有的代码更改都会被保留在暂存区中,等待你创建新的提交。
第七步:创建新的压缩提交
现在所有的更改都在暂存区中,创建一个新的提交:
1
| git commit -m "feat: 实现xxx功能"
|
第八步:验证结果
确认压缩操作成功:
1 2 3 4 5
| git log --oneline --graph -5
git log --oneline upstream/develop..feat-dev
|
一键脚本
为了方便日常使用,这里提供一个一键完成的Shell脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| #!/bin/bash
set -e
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m'
if [ $# -lt 2 ]; then echo -e "${RED}用法: $0 <目标分支> <提交信息>${NC}" echo -e "${YELLOW}示例: $0 upstream/develop \"feat: 实现用户登录功能\"${NC}" exit 1 fi
TARGET_BRANCH=$1 COMMIT_MSG=$2 CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
echo -e "${GREEN}========== Git Commit 压缩工具 ==========${NC}" echo -e "当前分支: ${YELLOW}${CURRENT_BRANCH}${NC}" echo -e "目标分支: ${YELLOW}${TARGET_BRANCH}${NC}" echo ""
if [ -n "$(git status --porcelain)" ]; then echo -e "${RED}错误: 工作区不干净,请先提交或暂存更改${NC}" exit 1 fi
if ! git rev-parse --verify "$TARGET_BRANCH" > /dev/null 2>&1; then echo -e "${RED}错误: 目标分支 ${TARGET_BRANCH} 不存在${NC}" exit 1 fi
echo -e "${GREEN}[1/6] 正在获取远程最新代码...${NC}" REMOTE_NAME=$(echo "$TARGET_BRANCH" | cut -d'/' -f1) git fetch "$REMOTE_NAME" 2>/dev/null || echo -e "${YELLOW}警告: 无法获取远程 ${REMOTE_NAME} 的最新代码${NC}"
COMMIT_COUNT=$(git log --oneline "$TARGET_BRANCH".."$CURRENT_BRANCH" | wc -l) echo -e "${GREEN}[2/6] 发现 ${YELLOW}${COMMIT_COUNT}${GREEN} 个提交需要压缩${NC}"
if [ "$COMMIT_COUNT" -eq 0 ]; then echo -e "${YELLOW}没有需要压缩的提交${NC}" exit 0 fi
echo -e "${GREEN}这些提交将被压缩:${NC}" git log --oneline "$TARGET_BRANCH".."$CURRENT_BRANCH" echo ""
BACKUP_BRANCH="${CURRENT_BRANCH}-backup-$(date +%Y%m%d%H%M%S)" echo -e "${GREEN}[3/6] 创建备份分支: ${YELLOW}${BACKUP_BRANCH}${NC}" git branch "$BACKUP_BRANCH"
echo -e "${GREEN}[4/6] 合并目标分支的最新代码...${NC}" if git merge "$TARGET_BRANCH" --no-edit 2>/dev/null; then echo -e "${GREEN}合并成功${NC}" else echo -e "${YELLOW}合并过程中发生冲突,请手动解决后重新运行此脚本${NC}" echo -e "${YELLOW}备份分支已创建: ${BACKUP_BRANCH}${NC}" exit 1 fi
echo -e "${GREEN}[5/6] 执行软重置...${NC}" git reset --soft "$TARGET_BRANCH"
echo -e "${GREEN}[6/6] 创建新的压缩提交...${NC}" git commit -m "$COMMIT_MSG"
echo "" echo -e "${GREEN}========== 操作完成 ==========${NC}" echo -e "原有 ${YELLOW}${COMMIT_COUNT}${NC} 个提交已压缩为 1 个" echo -e "备份分支: ${YELLOW}${BACKUP_BRANCH}${NC}" echo "" echo -e "${GREEN}最终提交记录:${NC}" git log --oneline -3
echo "" echo -e "${GREEN}相对于目标分支的提交:${NC}" git log --oneline "$TARGET_BRANCH".."$CURRENT_BRANCH"
echo "" echo -e "${YELLOW}提示: 如需恢复,请执行: git reset --hard ${BACKUP_BRANCH}${NC}"
|
脚本使用方法
-
将上述脚本保存为 squash-commits.sh
-
添加执行权限:
1
| chmod +x squash-commits.sh
|
-
执行脚本:
1
| ./squash-commits.sh upstream/develop "feat: 实现用户登录功能"
|
脚本功能说明
该脚本会自动完成以下操作:
- ✅ 检查工作区是否干净
- ✅ 获取远程仓库的最新代码
- ✅ 统计并显示要压缩的提交数量
- ✅ 创建带时间戳的备份分支
- ✅ 合并目标分支的最新代码(提前解决冲突)
- ✅ 执行软重置
- ✅ 创建新的压缩提交
- ✅ 显示操作结果
原理解释
为什么使用 git reset --soft?
Git reset 有三种模式:
| 模式 |
作用 |
HEAD |
暂存区 |
工作区 |
--soft |
只移动HEAD |
✅改变 |
❌不变 |
❌不变 |
--mixed(默认) |
移动HEAD和暂存区 |
✅改变 |
✅清空 |
❌不变 |
--hard |
全部重置 |
✅改变 |
✅清空 |
✅清空 |
使用--soft模式,所有的代码更改都保留在暂存区,只需要一个git commit就能将所有更改合并为一个提交。
与 git rebase -i 的对比
传统上,我们也可以使用交互式rebase来压缩提交:
1
| git rebase -i upstream/develop
|
然后在编辑器中将除第一个以外的所有pick改为squash或s。
两种方法的对比:
| 特性 |
reset --soft |
rebase -i |
| 操作复杂度 |
简单,一条命令 |
复杂,需要编辑器操作 |
| 保留提交信息 |
需要重新编写 |
可以合并原有信息 |
| 冲突处理 |
一次性处理 |
可能多次处理 |
| 适用场景 |
所有提交压缩为一个 |
灵活选择压缩哪些 |
注意事项
-
不要在公共分支上执行此操作:这会改变提交历史,如果其他人基于原有提交工作,会造成问题。
-
强制推送:压缩提交后,如果之前已经推送过,需要强制推送:
1
| git push -f origin feat-dev
|
-
备份很重要:虽然有reflog可以恢复,但创建备份分支更加直观和安全。
-
合并前先更新:先合并目标分支的最新代码,可以提前发现并解决冲突,避免提交PR后才发现问题。
总结
将多个杂乱的开发提交压缩成一个干净的提交,是提高代码仓库整洁度的好习惯。使用git reset --soft方法简单直接,配合本文提供的自动化脚本,可以大大简化这一操作流程。
记住三个关键步骤:
- 先更新 - 获取并合并上游最新代码
- 备份 - 创建备份分支以防万一
- 软重置后提交 - 使用
git reset --soft后创建新提交