Git
Basic
stashing
Stashing 是 Git 提供的一个非常有用的功能,它允许你临时保存工作目录和暂存区的更改,
而无需提交这些更改。这对于需要快速切换上下文但又不想创建不完整提交的情况特别有用。
作用:
- 临时保存未完成的工作
- 切换分支而不需要提交半成品
- 清理工作区以进行其他操作
保存当前更改到stash
git stash
# 或包含未跟踪的文件
git stash -u
# 或包含未跟踪文件和忽略的文件
git stash -a
查看stash列表
git stash list
恢复stash
# 恢复最近一次stash并保留stash记录
git stash apply
# 恢复指定stash
git stash apply stash@{n}
# 恢复最近一次stash并删除stash记录
git stash pop
** 删除stash**
# 删除最近一次stash
git stash drop
# 删除指定stash
git stash drop stash@{n}
# 删除所有stash
git stash clear
高级用法
# 为stash添加描述信息
git stash save "描述信息"
# 查看stash内容差异
git stash show -p stash@{n}
# 从stash创建分支
git stash branch 新分支名 stash@{n}
# 选择性恢复stash中的文件
git checkout stash@{n} -- 文件路径
场景
紧急修复bug
# 正在开发新功能时发现需要紧急修复
git stash
git checkout -b hotfix
# 修复bug并提交
git checkout feature-branch
git stash pop
切换分支
# 当前工作未完成但需要切换到其他分支
git stash
git checkout other-branch
# 完成其他工作后返回
git checkout original-branch
git stash pop
保存实验性代码
# 尝试一些实验性改动但不确定是否保留
git stash save "实验性改动"
# 如果决定保留
git stash pop
# 如果决定放弃
git stash drop
Git Flow管理
Git Flow的核心就是分支(Branch),通过在项目的不同阶段对分支的不同操作(包括但不限于创建、合并、变基等)来实现一个完整的高效率的工作流程。
Git Flow模型中定义了主分支和辅助分支两类分支。其中主分支包含主要分支和开发分支,用于组织与软件开发、部署相关的活动;辅助分支包含功能分支、预发分支、热修复分支以及其他自定义分支,是为了解决特定的问题而进行的各种开发活动。
与主分支不同,这些分支总是有有限的生命时间,因为它们最终将被移除。
包含分支
- 主分支
- master分支
- develop分支
- 辅助分支
- feature分支
- release分支
- hotfix分支
主分支
主要分支上存放的是最稳定的正式版本,并且该分支的代码应该是随时可在生产环境中使用的代码。
master分支只存放历史发布(release)版本的源代码。
即用于存放对外发布的版本,任何时候在这个分支获取到的都是稳定的已发布的版本。
各个版本通过tag来标记。上图里的v0.1和v0.2就是tag。
如何撤回已 Push 的代码
git revert
git revert 是一种安全的撤销方式,它不会修改历史记录,而是通过创建一个新的提交来撤销指定提交的更改。
# 1. 查看提交历史,找到你想要撤销的提交的 commit ID。
git log
# 2. 执行 git revert
git revert <commit-id>
# 如果需要撤销多个连续的提交,可以使用:
git revert <commit-id1> <commit-id2> ...
# 或者
git revert --no-commit <commit-id1> <commit-id2> ...
git commit -m "Revert multiple commits"
# 3. 推送
git push origin <branch-name>
master 分支禁止强制
使用 Git 原生保护机制
# 禁止所有用户强制推送
git config receive.denyNonFastForwards true
# 或者针对特定仓库设置
cd /path/to/repo.git
git config receive.denyNonFastForwards true
在后台配置
- 进入仓库 Settings → Repository
- 展开 "Protected branches"
- 选择 master 分支
- 勾选 "Prevent force push" 选项
git rebase
Git 中一个非常强大的工具,它允许你重新组织提交历史,使历史更加清晰和线性。
然而,它也会修改提交的历史记录,因此在使用时需要格外小心,尤其是在已经推送到远程仓库的分支上。
用途:
- 清理提交历史:将多个小的提交合并成一个或几个更有意义的提交。
- 更新分支:将你的分支的最新更改应用到主分支(如 main 或 master)上。
- 调整提交顺序:重新排列提交的顺序,使代码的提交历史更加合理。
清理本地提交历史
如果你在本地开发时产生了多个小的提交,可以通过 rebase 将它们合并为一个或几个更有意义的提交。
# N 是你想要回溯的提交数量。
git rebase -i HEAD~N
编辑提交历史
这时会打开一个文本编辑器,列出最近的 N 个提交。你可以选择以下操作: pick:保留这个提交。 squash 或 s:将这个提交与前一个提交合并。 reword 或 r:编辑这个提交的信息。 drop:删除这个提交。
完成变基
保存并关闭编辑器后,Git 会根据你的指令重新应用提交。
如果在变基过程中出现冲突,Git 会暂停并提示你解决冲突,然后继续变基。
强制推送到远程仓库
git push origin <branch-name> --force
将本地分支更新到最新版本
如果你的本地分支落后于远程分支,可以通过 rebase 将远程分支的最新更改应用到你的本地分支。
# 获取远程分支的最新更改
git fetch origin
# 开始变基
git rebase origin/<branch-name>
# 解决冲突(如果有)
git add <conflicted-files>
git rebase --continue
# 如果一切顺利,变基完成后,你的本地分支将包含远程分支的最新更改。
# 推送到远程仓库
git push origin <branch-name>
注意:
- 不要在公共分支上使用 rebase 修改历史:如果你的分支已经推送到远程仓库并且其他人可能依赖这些提交,使用 rebase 修改历史可能会导致问题。在这种情况下,推荐使用 git merge 来整合更改。
- 备份分支:在进行 rebase 之前,建议创建一个备份分支,以便在出现问题时可以恢复。
git branch backup-branch
- 解决冲突:在变基过程中,如果出现冲突,需要手动解决冲突并继续变基。如果变基过程变得复杂,可以随时中断变基:
git rebase --abort
- 然后重新开始。
Git中如何解决冲突?
在Git中解决冲突通常包括以下步骤:
- 识别冲突: 通过Git命令识别出发生冲突的文件。
- 手动编辑: 打开冲突文件,查找标记的冲突区域,并手动解决这些差异。
- 标记为已解决: 在解决了所有冲突后,使用git add命令将文件标记为冲突已解决。
- 完成合并: 执行git commit完成合并操作。在解决冲突的过程中,重要的是要理解不同分支上的变更,确保合并后的文件符合预期。
Cherry-pick
Git的Cherry-pick命令用于将选定的提交(commit)应用到其他分支。 适用场景包括:
- 特定更改的移植: 当需要将一个分支上的特定提交应用到当前分支时。
- 代码回退: 快速撤销已合并到主分支的更改。
- 修复分支: 在开发分支中修复错误后,将修复应用到其他分支。
Cherry-pick是一种灵活的工具,可以在不同分支间迅速移动更改,但需要谨慎使用,以避免潜在的合并冲突。
Tag是什么
Git的Tag是指向特定提交的引用,用于标记特定的点,如版本发布。
常用场景包括:
- 版本发布: 标记产品的重要版本,如v1.0、v1.1等。
- 里程碑: 标记项目的重要阶段或里程碑。
- 固定点参考: 提供一个固定的参考点,用于代码审查或回退。
Tag为项目的历史提供了清晰的参考点,便于跟踪和维护。
Submodule
Git的Submodule允许将一个Git仓库作为另一个Git仓库的子目录。
使用方法包括:
- 添加Submodule: 使用
git submodule add <repository> <path>
将子仓库添加到父仓库。 - 初始化Submodule: 克隆包含Submodule的仓库后,使用git submodule init初始化。
- 更新Submodule: 使用git submodule update拉取子模块的最新更改。
- 管理更改: 在Submodule中的更改需要在子仓库和父仓库中分别提交。
Submodule适用于管理依赖库或独立开发的模块,但需要额外的步骤来管理和更新。
Git中的HEAD、index
- HEAD: HEAD是指向当前分支上最新提交的指针。它是一个引用,指向当前分支的最后一次提交,代表最近的工作状态。
- index(暂存区): 暂存区是一个临时存储区域,用于记录即将提交到仓库的文件变更。当你执行git add命令时,变更就会被添加到暂存区。
- 工作目录: 工作目录包含项目的文件和目录,是用户进行编辑和修改的地方。工作目录的改动尚未被提交到Git仓库。
Git rebase和merge的区别是什么?
Git的rebase和merge都用于整合来自不同分支的更改,但它们的方式和结果有所不同:
- merge: 合并会创建一个新的“合并提交”,它将两个分支的历史整合在一起。这保留了完整的历史记录,但可能会导致复杂的提交图。
- rebase: rebase会重新排列提交,它将一个分支的提交应用到另一个分支的顶部。这会产生一个更线性的提交历史,但会改变现有分支的提交历史。
Git中撤销最后一次提交?
在Git中撤销最后一次提交可以通过以下命令实现:
git reset --soft HEAD~1
: 这个命令将HEAD指针移回到前一个提交,撤销提交但保留更改内容在暂存区。git reset --hard HEAD~1
: 如果想完全撤销最后一次提交(包括更改),可以使用这个命令。但要注意,这将丢失最后一次提交的所有更改。
查看文件的变更历史?
在Git中查看文件的变更历史可以通过以下命令实现:
git log <file>
: 显示特定文件的提交历史。git blame <file>
: 显示文件每一行的最后修改者和修改时间。git diff <file>
: 比较工作目录中的文件和暂存区或最后一次提交的差异。
三种合并策略
- 默认合并(Merge): 这是Git的标准合并策略,当你运行git merge时,默认使用。它会创建一个新的合并提交来组合两个分支的更改。
- 变基(Rebase): git rebase命令会将一个分支的所有更改应用到另一个分支的顶部。这可以创建一个更清晰的项目历史,但可能会改变现有的提交历史。
- 压缩合并(Squash): 在合并时使用git merge --squash可以将所有更改压缩成一个提交。这使得历史更整洁,但可能会丢失一些合并分支的历史细节。
恢复已删除的分支?
恢复已删除的Git分支可以通过以下步骤实现:
- 找到最后一次提交: 使用git reflog查找最后一次提交到删除的分支的记录。
- 创建新分支: 使用找到的提交哈希值,通过
git checkout -b <new-branch> <commit-hash>
命令创建一个新分支。
如何管理子模块
Git子模块的管理包括以下步骤:
- 添加子模块: 使用
git submodule add <repository> <path>
命令将子模块添加到仓库中。 - 初始化子模块: 在克隆包含子模块的仓库后,运行git submodule init初始化子模块。
- 更新子模块: 使用git submodule update命令获取子模块的最新更改。
- 管理子模块的更改: 子模块的更改需要在其自己的仓库中管理,并且需要在主仓库中更新子模块的引用。
撤销已推送到远程仓库的提交?
撤销已推送到远程仓库的提交可以通过以下步骤完成:
- 撤销最近的提交: 使用git revert HEAD创建一个新的提交,它将撤销最近的一次提交。
- 强制推送更改: 如果需要完全移除提交历史,可以使用git push --force,但这是一个危险操作,可能会影响所有协作者。
Git Hooks
Git Hooks可以在不同的Git事件触发时运行自定义脚本,提高开发效率:
- 配置Hooks: Git仓库中的.git/hooks目录包含多种钩子脚本,如pre-commit、post-commit等。
- 自定义脚本: 可以编写自定义脚本并放在相应的钩子文件中,比如在pre-commit钩子中运行代码质量检查。
- 自动化任务: 钩子可以用来自动化各种任务,例如自动运行测试、代码格式化、发送通知等。
Git 基础命令
分支管理
创建与切换分支:git branch、git checkout、git switch合并分支:git merge、git rebase删除与重命名分支:git branch -d、git branch -m
提交历史管理
撤销提交:git reset、git revert修改提交:git commit --amend、git rebase -i查看历史:git blame、git reflog
冲突解决
参考: