Git 命令

63 阅读11分钟

前置文章:Git 下载 、安装、规范

本文主要针对以下内容做详细讲解:

  • stashcherry-pickresetrevert 命令;
  • 文件状态管理(暂存区、工作区操作);
  • 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 分支合并到主分支
  • 选择性合并提交,避免整个分支合并

详细场景示例:

  1. 正在开发的模块的其中一个功能要先上线,其余部分没开发完,这时就需要将那一块 commit 抽出来到新的分支进行合并;
  2. 分支的 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

解决方式

  1. 保留当前分支的内容(删除 =======>>>>>>> branch-name 之间的内容)
  2. 保留要合并分支的内容(删除 <<<<<<< HEAD======= 之间的内容)
  3. 保留两部分内容(删除冲突标记,保留两个分支的内容)
  4. 完全重写这部分内容

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 LabMerge 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