前置文章:Git 下载 、安装、规范
本文主要针对以下内容做详细讲解:
stash、cherry-pick、reset、revert命令;- 文件状态管理(暂存区、工作区操作);
- Merge 冲突解决完整流程
- 可视化操作对应的命令
- 开发工作流程规范
stash
临时保存当前工作目录的修改,让工作区回到干净状态;若使用 commit 则会多出一条提交记录, 后续想保持干净的提交记录还得执行 teset --soft 撤回提交。stash 默认会同时保存 工作区 和 暂存区 的文件修改。
- 工作区: 正在编辑还没执行
git add的文件; - 暂存区: 已经执行
git add但还没有commit的文件;
使用场景:
- 需要快速切换分支,但当前有未提交的修改
- 临时保存实验性代码,避免污染提交历史
- 紧急修复 bug 时需要保存当前进度
常用命令:
# 保存并添加描述信息
git stash save "描述信息"
# 查看所有stash
git stash list
# 恢复最新的stash, 并删除 stash 记录
git stash pop
# 恢复指定的stash(不删除)
git stash apply stash@{0}
# 删除指定的stash
git stash drop stash@{1}
# 清空所有stash
git stash clear
特殊情况:
# 只保存工作区的修改
git stash --keep-index
# 只保存暂存区的修改
git stash --staged
# 同时保存未跟踪的文件(新建文件)
git stash --include-untracked
cherry-pick
将指定的提交应用到当前分支。简单来说就是将某一个 commit 提交记录, copy 复制一份到别的分支, 提交的代码改动就带过去了。
使用场景:
- 将bug修复从开发分支应用到生产分支
- 将特定功能从
feature分支合并到主分支 - 选择性合并提交,避免整个分支合并
详细场景示例:
- 正在开发的模块的其中一个功能要先上线,其余部分没开发完,这时就需要将那一块
commit抽出来到新的分支进行合并; - 分支的 git 记录被污染,混乱了,合并时有问题,可以拉一条新分支,从旧分支将开发功能的
commit提出来复制到新分支;
常用命令:
# 查看日志信息, 复制好所需commit的hash值
git log
# 应用指定提交
git cherry-pick <commit-hash>
# 应用多个提交
git cherry-pick <commit1> <commit2>
# 应用提交范围
git cherry-pick <start-commit>..<end-commit>
# 只应用修改,不自动提交
git cherry-pick -n <commit-hash>
# 解决冲突后继续
git cherry-pick --continue
# 回到操作cherry-pick前的样子,就像什么都没发生过。
git cherry-pick --abort
# 保留已经 cherry-pick 成功的 commit,并退出 cherry-pick 流程。
git cherry-pick --quit
reset
重置 HEAD 指针到指定位置,可选择是否保留工作区修改。仅限于 本地还没有 push 到远程的 commit ,原有commit记录会消失(git log 看不到,但是通过 git reflog 可以看到)。若要覆盖远程仓库, 需要使用 push -u。
使用场景:
- 撤销最近的提交
- 回退到某个历史版本
- 清理提交历史
三种模式:
HEAD^ 可以写成 HEAD~1, 1代表撤回一个commit
# --mixed: 移动HEAD和暂存区,保留工作区(默认)。 (不撤销文件修改, 修改的文件还会在工作区)
git reset --mixed HEAD~1
git reset HEAD~1 # 同上
# --soft: 只移动HEAD,保留暂存区和工作区 (不撤销文件修改, 修改的文件还会在暂存区)
git reset --soft HEAD^
# --hard: 移动HEAD、暂存区和工作区(危险操作),完全撤销掉文件修改,内容丢失
git reset --hard HEAD~1
# 回退到指定提交
# 注意: 若commitId不是最新的一次提交,他会将 commitId 到 最新一次提交,所有的 commit 全都撤回
git reset --hard <commit-hash>
# 回退到远程分支状态
git reset --hard origin/main
revert
创建一个新提交来撤销指定提交的修改。跟reset不同,他可以指定还原(撤回)某一个commit,并生成一条revert的操作记录。如果 revert 的那一次 commit 操作是 merge 操作,revert 会失败;
使用场景:
- 撤销已推送到远程的提交
- 安全地回退公共分支的修改
- 保持提交历史的完整性
场景示例:
A提了一个功能commit,B在A的后面提了一个功能commit。 然后,A的功能有问题,需要马上撤回。因为在A的后面有其他人也提交了commit,所以 如果使用 reset 的话,会将别人的代码也撤回掉,这时就需要使用revert了
常用命令:
# 撤销指定提交
git revert <commit-hash>
# 撤销多个提交
git revert <commit1> <commit2>
# 撤销提交范围
git revert <start-commit>..<end-commit>
# 只创建revert提交,不自动提交
git revert -n <commit-hash>
# 撤销合并提交
git revert -m 1 <merge-commit-hash>
# 遇见冲突
# 通过abort退出revert操作
git revert --abort
# 也可以 通过解决冲突,执行continue继续revert
git revert --continue
# 或者通过 skip 跳过这个冲突
git revert --skip
git revert 撤销 merge 操作的详细处理
当要撤销的 commit 是一个 merge 操作时, Git 不知道要撤销哪个分支的修改,因为 merge commit 有两个父提交。
解决方案:
# 查看merge commit的详细信息
git show <merge-commit-hash>
# 撤销merge操作,-m 1 表示撤销第一个父分支的修改
git revert -m 1 <merge-commit-hash>
# 撤销merge操作,-m 2 表示撤销第二个父分支的修改
git revert -m 2 <merge-commit-hash>
如何确定使用 -m 1 还是 -m 2:
# 查看merge commit的父提交
git log --pretty=format:"%h %s" <merge-commit-hash>^1 # 第一个父提交
git log --pretty=format:"%h %s" <merge-commit-hash>^2 # 第二个父提交
# 通常:
# -m 1: 撤销被合并分支的修改(通常是feature分支)
# -m 2: 撤销主分支的修改(通常是main/master分支)
实际场景示例:
# 场景:main分支合并了feature分支,现在要撤销这个合并
# 1. 查看merge commit
git log --oneline --graph
# 2. 撤销合并(撤销feature分支的修改)
git revert -m 1 <merge-commit-hash>
# 3. 如果后续还要重新合并feature分支,需要先revert这个revert
git revert <revert-commit-hash>
文件状态管理
1. 将暂存区的文件撤回到工作区
场景: 文件已经执行了 git add 添加到暂存区,现在想撤销这个操作
# 撤销指定文件的暂存状态,回到工作区
git reset HEAD <file-path>
git reset HEAD src/components/Button.js
# 撤销所有文件的暂存状态,回到工作区
git reset HEAD
# 或者使用更直观的命令
git restore --staged <file-path>
git restore --staged src/components/Button.js
# 撤销所有文件的暂存状态
git restore --staged .
2. 撤销工作区文件的修改
场景: 工作区的文件被修改了,想要完全撤销这些修改,回到最后一次提交的状态
# 撤销指定文件的修改
git checkout -- <file-path>
git checkout -- src/components/Button.js
# 撤销所有文件的修改
git checkout -- .
# 或者使用更直观的命令
git restore <file-path>
git restore src/components/Button.js
# 撤销所有文件的修改
git restore .
3. 文件状态查看
# 查看文件状态
git status
# 查看工作区与暂存区的差异
git diff
# 查看暂存区与最新提交的差异
git diff --cached
# 查看工作区与最新提交的差异
git diff HEAD
4. 文件状态管理总结
| 操作 | 命令 | 说明 |
|---|---|---|
| 暂存区 → 工作区 | git reset HEAD <file> | 撤销add操作 |
| 暂存区 → 工作区 | git restore --staged <file> | 撤销add操作(新命令) |
| 工作区 → 最后提交 | git checkout -- <file> | 撤销文件修改 |
| 工作区 → 最后提交 | git restore <file> | 撤销文件修改(新命令) |
5. 可视化操作对应的文件状态管理
可视化操作: 在文件列表中取消勾选已暂存的文件 对应命令:
git restore --staged <file-path>
可视化操作: 在文件列表中右键选择 撤销 - Discard Changes
对应命令:
git restore <file-path>
6. 此次commit与上一次commit合并
适用于上一次commit已经推送到远程仓库,并且这个分支最好只有自己本人开发。若上一次commit没有推送到远程仓库,则撤销上一次commit,重新commit即可。
git add .
# 1.将当前staged的修改合并到上一次commit中,
# 2.保持了相同的commit消息
# 3.替换新的commit hash
# 4.这时,在本地git历史会看到两条提交信息一样的commit,推送到远程仓库后就会合并成一条
git commit --amend --no-edit
# 1.强制推送到远程仓库,更新远程的commit历史
# 2. --force-with-lease 比 --force 更安全,它会检查远程分支是否有其他人的新提交
git push --force-with-lease
注意事项:
-
由于使用了 --force-with-lease,如果其他团队成员已经基于之前的commit进行了开发,他们需要重新同步分支
-
如果这是一个共享分支,建议通知团队成员这个变更
Git Merge 冲突解决
冲突解决流程
1. 识别冲突文件
# 查看哪些文件有冲突
git status
# 输出示例:
# Unmerged paths:
# both modified: src/components/Button.js
# both modified: src/utils/helper.js
2. 查看冲突内容
# 查看冲突文件的详细内容
git diff
# 查看特定文件的冲突
git diff <file-path>
3. 手动解决冲突
冲突文件中的标记说明:
<<<<<<< HEAD
当前分支的内容
=======
要合并分支的内容
>>>>>>> branch-name
解决方式:
- 保留当前分支的内容(删除
=======到>>>>>>> branch-name之间的内容) - 保留要合并分支的内容(删除
<<<<<<< HEAD到=======之间的内容) - 保留两部分内容(删除冲突标记,保留两个分支的内容)
- 完全重写这部分内容
3.1. 冲突解决策略
快速选择冲突文件中要保留的部分。
策略1: 接受当前分支的修改
# 对于特定文件,接受当前分支的版本
git checkout --ours <file-path>
# 对于所有冲突文件,接受当前分支的版本
git checkout --ours .
策略2: 接受要合并分支的修改
# 对于特定文件,接受要合并分支的版本
git checkout --theirs <file-path>
# 对于所有冲突文件,接受要合并分支的版本
git checkout --theirs .
4: 将解决完冲突的文件提交到暂存区
# 将解决冲突的文件添加到暂存区
git add <file-path>
git add src/components/Button.js
# 或者添加所有已解决冲突的文件
git add .
步骤5: 完成合并,所有文件commit
# 完成合并提交
git commit
# 或者使用默认的合并信息
git commit --no-edit
放弃, 退出 合并
# 如果决定不进行合并,可以放弃
git merge --abort
# 这会回到合并前的状态,就像什么都没发生过
Git可视化操作对应的命令
1. 添加指定文件到暂存区
可视化操作: 在文件列表中勾选特定文件,点击 Add
对应命令:
git add <file-path>
git add src/components/Button.js
2. 提交指定文件
可视化操作: 选择特定文件,填写提交信息,点击 Commit
对应命令:
git add <file-path>
git commit -m "提交信息"
3. 撤回上一次提交
可视化操作: 在提交历史中右键选择 Undo Last Commit
对应命令:
# 保留修改在工作区
git reset HEAD~1
# 保留修改在暂存区
git reset --soft HEAD~1
# 完全删除修改(危险)
git reset --hard HEAD~1
4. 修改上一次提交的提交信息
可视化操作: 在提交历史中右键选择 Edit Commit Message
对应命令:
git commit --amend -m "新的提交信息"
5. 将最近两次提交合并成一个
可视化操作: 选择两个提交,右键选择 Squash Commits
对应命令:
# 交互式rebase
git rebase -i HEAD~2
# 在编辑器中:
# pick <第一个提交hash> 第一个提交信息
# squash <第二个提交hash> 第二个提交信息
# 保存后编辑合并后的提交信息
开发新需求
1、分支名规范
【release / main】:生产
【develop】:开发
【test】:测试
【uar】:uat
【feature / 简写feat】:按照各个功能点拉取分支,feat分支一般以禅道任务单号为单位从release分支中拉取一个新的分支。
2、新功能开发
要确保当前分支代码的纯粹性,尽量不能有其他功能需求的分支代码
# 切换到 release(生产环境) 分支
git checkout release
# 更新本地 release 分支
git pull origin release
# 以 release 分支切出 feat#00001 分支 #00001为任务单号(没有单号,自己知道是哪个功能的就行)
git checkout -b feat#00001
#将新分支推到远程仓库
git push origin feat#00001
3、提交代码,打包发布
本地能直接 push 分支的情况。一般会在 Git Lab 走 Merge Request 流程;
# 发生产则在release, 发测试操作develop分支
# 切到develop || release
git checkout develop
# 拉取最新
git pull origin develop
# 将 功能feat#00001 分支合并到主分支,有冲突解决冲突
git merge feat#00001
# 将最新分支代码推到远程仓库
git push origin develop
# 打包代码(一般走 CI / CD 流程)
npm run build:develop
4、Merge Request 有冲突;
在本地,丛 要合并的分支 切一个分支出来,merge 功能分支, 本地解决冲突后,将分支 push 推上去,重新走 merge request, 源分支改成刚刚解决掉冲突的分支即可;
例:
# 切分支
git checkout release
git checkout -b release-mr
# 合并
git merge feat#1
......
# 推送
git push origin release-mr
# merge request
release merge release-mr