基础
Git 的三种状态
- 已提交 (committed) - 数据已经安全地保存在本地数据库中
- 已修改 (modified) - 修改了文件,但还没有保存到数据库
- 已暂存 (staged) - 对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中
Git 项目的三个阶段及工作流
- 工作区 - 在工作区中修改文件
- 暂存区 - 可以在暂存区对下次提交的更改选择性地暂存
- Git目录 - 提交更新找到暂存区的文件,将快照永久性存储到 Git 目录
配置
变量存储位置
- /etc/gitconfig 文件:系统上每一个用户及其仓库的通用配置,
# 查看所有配置以及她们所在的文件
git config --list --show-origin
本地操作
基础篇
- commit
git commit
- branch
早建分支,多用分支
git checkout -b bugFix
- merge
git checkout -b bugFix
git commit
git checkout main
git merge bugFix
- rebase
git checkout -b bugFix
git commit
git checkout main
git commit
git checkout bugFix
git rebase main
高级篇
- 分离 HEAD
默认 HEAD 指向分支,可以使 HEAD 脱离分支
git checkout commitID
相对引用
^
向上移动一个提交记录~<num>
向上移动多个提交记录,如~3
git checkout bugFix^
- 强制修改分支位置
# 将 main 分支强制指向 HEAD 的第 3 级父提交
git branch -f main HEAD~3
撤销变更
- reset
通过把分支记录回退几个提交记录来实现撤销改动。可以将这想象成“改写历史”。git reset 向上移动分支,原来指向的提交记录就跟从来没有提交过一样。 - revert
git reset
很方便,但是这种“改写历史”的方法对远程分支是无效的!为了撤销更改并分享给别人,需要使用git revert
- reset
git reset HEAD^ # 撤销(删除)前一个提交
git revert HEAD # 以新增提交形式恢复到上一个提交
移动提交记录
- git cherry-pick
git cherry-pick <commitID> [<commitID>...]
将一些提交赋值到当前所在位置(HEAD)下面 - 交互式 rebase
如果不清楚想要的提交记录的哈希值,可以利用交互式的 rebaserebase --interactive
或rebase -i
git rebase -i HEAD~4 # 然后在交互窗口进行 reorder 和 pick
补充
- 修复 bug 过程中,去掉调试提交,只取一个修复提交
git checkout main # 切换到主分支
git cherry-pick targetCommitID # 摘取需要的提交到 main 分支
修改过去的某一次提交的内容
rebase 思路:- 先用
git rebase -i
将提交重新排序,然后把想要修改的提交记录挪到最前 - 然后用
git commit --amend
来进行一些小修改 - 接着再用
git rebase -i
来将他们调回原来的顺序 - 最后把 main 移到修改的最前端(
git rebase
或git merge
)
cherry-pick 思路:
- 先切换到 main 分支
git checkout main
- 将要修改的提交摘到 main 分支
git cherry-pick c2
- 对摘到的提交进行修改
git commit --amend
- 将被摘提交后面的提交摘到 main 分支
git cherry-pick c3
- 先用
- git tags
永远指向某个提交记录的标识,不随新的提交而移动,就像提交树上的一个锚点,标识某个特定的位置
git tag v1 c1
# 等价于
git checkout c1
git tag v1
git describe
描述离你最近的标签git describe
# git describe <ref> # <ref> 任何能被 git 识别成提交记录的引用 # 输出 <tag>_<numCommits>_g<hash> # <tag> 离 ref 最近的标签 # <numCommits> ref 与 tag 相差多少个提交记录 # <hash> 给定 ref 对应 hash
高级操作
多分支 rebase
git checkout bugFix git rebase main git checkout another git rebase side git rebase bugFix git branch -f main HEAD
两个父节点
^<num>
指定合并提交记录的某个父亲提交,git 默认选择合并提交的第一个父亲,可以在^
后加数字改变默认行为git checkout main^ # 等价于 git checkout main git checkout main^2 # 切换到合并提交的第二个父提交 git checkout HEAD~^2~2 # 切换到当前提交的第二个父提交的倒数第二个提交
git checkout HEAD~^2~1 git checkout -b bugWork git checkout main
远程操作
- clone
git clone # 克隆远程仓库
远程分支命名规范 <remote name>/<branch name>
默认远程仓库名为 origin
fetch
- 从远程仓库下载本地仓库中缺失的提交记录
- 更新远程分支指针(如 o/main)
git fetch # 从远程仓库获取数据
pull
git fetch
和git merge
的缩写
- push
负责将变更上传到指定的远程仓库,并在远程仓库上合并新提交记录 - git pull --rebase
fetch
和rebase
的缩写 - Remote Rejected
master 被锁定时, 需要一些 Pull Request 流程来合并修改
新建一个分支, 推送(push)这个分支并申请 pull request
推荐 git 工作流
GitFlow 是由 Vincent Driessen 提出的一个 git 操作流程标准。
名称 | 说明 |
---|---|
master | 主分支 |
develop | 主开发分支,包含确定即将发布的代码 |
feature | 新功能分支,一般一个新功能对应一个分支,对于功能的拆分需要合理,避免后面一些不必要的代码冲突 |
release | 发布分支,发布时用的分支,一般测试时发现的 bug 要在这个分支进行修复 |
hotfix | hotfix 分支,紧急修 bug 时用 |
GitFlow 优势
- 并发开发:每个新功能都会建立一个新的 feature 分支,从而和已经完成的功能隔离开来,而且只有在新功能完成开发的情况下,其对应的 feature 分支才会合并到主开发分支上(develop 分支)。另外,如果你正在开发某个功能,同时又有一个新的功能需要开发,只需要提交当前 feature 代码,然后创建另一个 feature 分支并完成新功能开发。然后再切回之前的 feature 分支即可继续完成之前功能的开发。
- 协作开发:GitFlow 还支持多人协同开发,因为每个 feature 分支上改动的代码都只是为了让某个新的 feature 可以独立运行,同时我们也很容易知道每个人都在干啥。
- 发布阶段:当一个新 feature 开发完成的时候,会被合并到 develop 分支,这个分支主要来暂存那些还没有发布的内容,所以如果需要再开发新的 feature,只需要从 develop 分支创建新分支,即可包含所有已完成的 feature。
- 支持紧急修复:GitFlow 还包含了 hotfix 分支。这种类型的分支是从某个已经发布的 tag 上创建出来并做一个紧急的修复,而且这个紧急修复只影响这个已经发布的 tag,而不会影响到正在开发的新 feature。
Q&A
rebase 和 merge 的区别
git rebase 和 git merge 一样都是用于从一个分支获取并且合并到当前分支。假设有一个场景,[feature/todo] 分支要合并到 master 主分支,那么用 rebase 或者 merge 有什么不同呢?
- merge 特点:自动创建一个新的 commit 如果合并的时候遇到冲突,仅需要修改后重新 commit
优点:记录了真实的 commit 情况,包括每个分支的详情
缺点:因为每次 merge 会自动产生一个 merge commit,所以在使用一些 git 的 GUI tools,特别是 commit 比较频繁时,看到分支很杂乱 - rebase 特点:会合并之前的 commit 历史
优点:得到更简洁的项目历史,去掉了 merge commit, 所有提交都在一条线上
缺点:如果合并出现代码问题不容易定位,因为 re-write 了 history
因此,当需要保留详细的合并信息的时候建议使用 git merge,特别是需要将分支合并进入 master 分支时;当发现自己修改某个功能时,频繁进行了 git commit 提交时,发现其实过多的提交信息没有必要时,可以尝试 git rebase
git reset、git revert 和 git checkout 有什么区别
git 仓库的三个组成部分:
- 工作区:在 git 管理下的正常目录都算工作区,平时的编辑工作都是在工作区完成
- 暂存区:临时区域,里面存放将要移交文件的快照
- 历史记录区:git commit 后的记录区
三个去的转换关系以及转换所使用的命令:
git reset、git revert 和 git checkout 的共同点:用来撤销代码仓库中的某些更改。区别是:
从 commit 层面讲:
git reset 可以将一个分支的末端指向之前的一个 commit。下次 git 执行垃圾回收的时候,会把这个 commit 之后的 commit 都扔掉。git reset 还支持三种标记,用来标记 reset 指令影响的范围:
- --mixed 会影响到暂存区和历史记录区,是默认选项
- --soft 只影响历史记录区
- --hard 影响工作区、暂存区和历史记录区
注意:因为 git reset 是直接删除 commit 记录,从而会影响到其他开发人员的分支,所以不要在公共分支做该操作
- git checkout 可以将 HEAD 移到一个新的分支,并更新工作目录。因为可能会覆盖本地的修改,所以执行这个指令之前,需要 stash 或者 commit 暂存区和工作区的更改
- git revert 和 git reset 的目的是一样的,但做法不同,它会以创建新的 commit 的方式来撤销 commit,这样能保留之前的 commit 历史,比较安全。另外,同样因为可能会覆盖本地的修改,所以执行这个指令之前,需要 stash 或 commit 暂存区和工作区的更改。
从文件层面说:
- git reset 只是把文件从历史记录区拿到暂存区,不影响工作区的内容,而且支持 --mixed、--soft 和 --hard
- git checkout 这是把文件从历史记录区拿到工作区,不影响暂存区的内容
- git revert 不支持文件层面的操作。