前言
为什么要了解 git?
I'm gonna make him an offer he can't refuse. (我将给他一个他无法拒绝的条件) —— 教父
😀
git 传输协议
协议 | url 格式 |
---|---|
本地协议 | /User/userName/project/.git |
file 协议 | file:///User/userName/project/.git |
http(s) 协议 | http(s)://github.com/user/project.git |
ssh 协议 | git@exmple.com:user/project.git |
常用 Git 命令
help
# 显示 git 帮助信息
$ git help
# 显示 help 命令的帮助信息
$ git help help
# 使用浏览器查看 Git 某个命令的帮助信息
$ git help --web [命令]
# 使用浏览器查看查看 log 命令的帮助信息
$ git help --web log
config
config 作用域
- global:对当前用户所有仓库有效
- local:只对某个仓库有效
- system:对系统所有登录的用户有效
# 配置 user 信息
$ git config --global user.name 'your_name'
$ git config --global user.email 'your_email@domain.com'
# 显示所有配置
$ git config --list --global
$ git config --list --local
$ git config --list --system
init 创建仓库
$ git init [project]
# 空白工作目录
$ git init --bare [project]
clone 克隆仓库
# 克隆仓库默认主分支
$ git clone [url]
# 克隆指定分支
$ git clone -b [branch] [url]
# 例子
$ git clone /User/userName/project/.git
$ git clone file:///User/userName/project/.git
$ git clone https://github.com/user/project.git
$ git clone git@github.com:user/project.git
add 将文件添加进暂存区
# 可以指定文件或文件夹,可以是多个文件,用空格隔开
$ git add 'specify_file'
# 添加所有
$ git add .
# 仅将工作区中暂存文件更新到暂存区
$ git add -u
commit 提交到版本历史管理
# 提交信息
$ git commit -m 'message'
# 没有新增文件时,可用-am省略 $ git add 步骤,但是就不会添加进暂存区
$ git commit -am 'message'
# 指定commit 1个或多个文件
$ git commit -m 'message' specify_file
$ git commit -m 'message' specify_file1 specify_file2 specify_file3
# 修改最近的一次commit
$ git commit --amend
push 推送到远端
# 推送
$ git push
# 没有新增文件时,可用-am省略 $ git add 步骤,但是就不会添加进暂存区
$ git push --set-upstream <远程主机名> <分支>
fetch 将远端的最新内容拉到本地
$ git fetch
pull 拉取
git fetch + $ git merge
# 拉取远端最新内容并与本地自动合并
$ git pull
# 拉取远端指定分支最新内容并与本地自动合并
$ git pull <远程主机名> <分支>
status 查看文件管理区状态
$ git status
log 查看提交日志
# 查看提交日志
$ git log
# 单行显示
$ git log --oneline
# 指定查看最近的10个commit
$ git log -n10
# 图形化方式查看
$ git log --graph
# 合并使用
$ git log -n10 --oneline
$ git log --all --oneline
$ git log --all --graph
branch
# 查看本地有多少分支
# 删除分支
$ git branch -D [branch]
checkout
# 切换分支
$ git checkout [branch_name]
# 切换到当前分支的某个 commit,会进入 `detached HEAD`(分离头指针)状态
$ git checkout [commit]
# 创建分支
$ git checkout -b your_branch
# 根据某个 commit 创建分支
$ git checkout -b your commit
merge 合并
$ git merge <source_branch> <target_branch>
# 合并不同根(无父子关系)的分支
$ git merge --allow-unrelated-histories [target_branch]
tag 标签
# 设置一个标签
$ git tag [message]
rm 删除文件
$ git rm specify_file
$ git rm --cached specify_file
mv 重命名
$ git mv specify_file new_file
reset 重置
# 重置所有到暂存区状态
$ git reset HEAD
# 工作区某个或多个文件恢复到暂存区状态
$ git reset HEAD -- file
$ git reset HEAD -- file1 file2
# 取消最近的提交
$ git reset --hard [commit]
$ git reset --hard HEAD
remote 远程仓库操作
# 显示所有“远程”仓库
$ git remote -v
# 添加远程仓库
$ git remote add [shortname] [url]
stash 暂存工作区
# 暂存工作区
$ git stash
# 查看暂存的工作区列表
$ git stash list
# 恢复最近一次暂存的工作区
$ git stash pop
# 恢复最近暂存的工作区作
$ git stash apply
# 恢复某个暂存的工作区
$ git stash apply stash@{1}
# 删除暂存工作区
# 丢弃最近一次暂存的工作区
$ git stash drop
# 删除所有的暂存的工作区
$ git stash clear
pop 和 apply 的区别是 pop 从 refs/stash 里暂存的工作区列表取出一个,而 apply 则保留 refs/stash 内容,
diff 比较差异
- 比较两个版本差异
# HEAAD^1^1 等同于 HEAAD^^ 等同于 HEAAD~2
$ git diff commit1 commit2
$ git diff HEAAD commit2
$ git diff HEAAD HEAAD^1
$ git diff HEAAD HEAAD^1^1
# 比较工作区和暂存区文件差异
$ git diff -- file
# 比较两个版本指定文件差异
$ git diff branch1 branch2 -- file
$ git diff commit1 commit2 -- file
cat-file 检测 git 对象
git 对象有commit、tree、blob,都有对应 hash 值表示
# 显示库对象信息
$ git cat-file -t your_hash
# 显示库对象内容
$ git cat-file -p your_hash
rebase 变基
该指令在团队合作中请谨慎使用,一般来讲只用来操作自己本地相关的未同步到远端的commit和分支
# 修改历史 commit 信息
$ git rebase -i commit
根据cmmadns提示,我们把要修改的commit 对应的pick改为rework(或者简写r)
然后退出保存(按 Esc 退出编辑模式,Shift + :,输入 WQ 然后回车),之后会自动跳转到如下页面
然后修改commit message内容退出保存即可
然后得到如下成功提示
值得注意的是这里其实是进入了分离头指针模式进行处理的
- 合并连续的 commit 操作跟上面类似,只是在修改Pick时,选择保留一个最上的pick,其下的选择 squash,然后在跳转的新窗口修改好新的commit即可
不连续的 commit 如果想要合并,只需在编辑的时候将其放到一起即可
gitk 以图形界面查看仓库
gitk
得到如下类似页面
git 对象
- commit:版本历史
- tree:一个commit对应一棵tree,这棵树存放了当前版本的所有文件及文件夹信息(快照),每个文件夹都是一颗子tree
- blob:文件,一棵tree可以由多个tree & blob组成(文件及文件夹的区别)
每个对象都有其对应的哈希值(hash)对应,所有文件只有一个源,由commit、tree、blob保存其信息,这样可以极大的减少存储空间
合并方案
- merge
把源分支指向目标分支并产生一个 merge commit 节点
# 切换到目标分支
$ git checkout <target_branch>
# 合并
$ git merge <source_branch>
- squash merge
把源分支多个 commit 合并成一个 commit 后再合并
# 切换到目标分支
$ git checkout <target_branch>
# 执行该命令
$ git merge --squash <source_branch>
- rebase merge
把源分支想多目标分支分离节点开始的多个 commit直接复制一份贴到目标分支后面,保持源分支线性独立的同时目标分支有源分支的一部分
# 进入源分支
1. $ git checkout <source_branch>
# 变基
2. $ git rebase -i <target_branch>
3. 如果有冲突则处理冲突
# 处理完添加到暂存区
4. $ git add .
# 继续 rebase ,此时还需要把上一步的冲突处理再处理一边,原因是变基导致的,有多少个相同的冲突就需要重复处理多少次,重复步骤3、4、5,直到没有冲突提示
5. $ git rebase --continue
# 切换到目标分支
6. $ git checkout <target_branch>
# 合并
7. $ git merge <source_branch>
.git 目录
- HEAD:由于 git 可以存在很多分支,每个分支指向一个 commit 对象的可变指针,而 Head 是一个指向你当前正在工作中的本地分支的指针,如果进入分离头指针状态,则是指向当前分支的某个commit
- config:git 相应配置记录在此文件,只影响当前库
Git Hooks
存放各种 Git 钩子的 .sample 文件,只要把文件名 .sample 去掉即可生效
Git logs
存放日志的文件夹
Git refs
存放版本历史以及里程碑
- heads:存放版本历史索引,HEAD 引用的则里面的内容
- tags:里程碑
Git objects
用来管理 git commit -m` 都要生成新的 commit对象快照
- info文件夹:
- pack文件夹:内容多了之后会打包到pack管理
- 其他文件夹(假设c4):里面有 commit ,把父文件夹名(c4)和commit拼到一起得到hash,使用git cat-file -t your_hash显示结果是:tree(工作树),git cat-file -p your_hash,会显示该commit的内容
SSH key
# 生成秘钥
ssh-keygen -t rsa -b 4096 -f yourkeyname -C "your_email@exmple.com"
# 拷贝秘钥内容
cd ~/.ssh
cat id_rsa.pub
参数 | 说明 |
---|---|
-t rsa | 采用rsa加密方式,t=type |
-b | 采用长度4096bit的密钥对,b=bits,最长4096 |
-f | 生成文件名,f=output_keyfiles |
-c | 备注,C=comment |
团队工作流
团队开发一定涉及到分工与协作,此时就需要对应的团队工作流来进行管理
选择工作流一般考虑以下几点
- 团队人员构成
- 研发设计能力
- 产品特征
- 项目大小、难易程度
在实际的团队项目管理过程可以参考以下几个工作流模式来设计合适的工作流
主干开发
主干开发(trunk-based development TBD)的特点是项目的功能开发工作主要在 master 主分支上
在这个特点之上有主干发布
、分支发布
两种发布方式
主干发布
适用于:
- 组件开发团队,成员能力强,人员比较少且沟通顺畅
- 用户升级成本底
分支发布
适用于:
- 开发团队开发能力强且系统设计能力也强
- 有完善且有效的特性切换实施方案,能保证上线后在无需修改代码前提下就修改系统行为
- 产品需要快速迭代,尽可能获得 CI / CD 最多好处
Git Flow
git-flow 模式会预设两个主分支在仓库中:
- master 只能用来持续集成。不直接提交改动到 master 分支上,也是很多工作流程的一个共同的规则
- develop 进行任何新功能开发的基础分支,且该分支也集成所有已经完成的功能,并等待被整合到 master 分支中
此外还有对应场景的分支,这些分支完成任务后会被删除:
- feature/xx 仅在开发过程中存在,在广义“开发”背景下完成验证后(全面测试)被集成到 develop即可删除。
- release/version 主要是用来管理和发布版本的,当 release 的功能以及修复都完成后会合并到 develop,并被删除
- hotfix/version 针对 release 的修复
适用于:
- 有严格发布周期
- 有严格发布流程
- 产品需要快速迭代,尽可能获得 CI / CD 最多好处
荷兰程序员 Vincent Driessen 的一篇博客,让 git-flow 策略广为人知。引用该博客的图片
GitHub Flow
GitHub Flow 是以部署为中心的开发模式,以简单的开发流程和完全自动化满足持续、高效、且安全的部署需求
其特点是:
- master 分支时常保持可以部署的状态
- 新的开发要从 master 分支创建新的分支
- 在本地新建的分支中进行提交,定期 push
- 以 Pull Request 进行交流
- 让其他开发者进行审查,确认开发完成后与 master 分支进行合并,合并的代码一定要进行测试
- 与 master 分支合并后,立刻部署
适用于:
- 重视测试团队
- 规模在 15-20 人的团队
- 随时集成随时发布(一天内多次实施部署)
- 只要通过相应的质量管理机制即可发布:代码评审和自动化测试等
GitLab Flow
GitLab Flow(无环境)模式只有两个主分支在仓库中:
- master分支负责持续集成
- production分支负责发布
适用于:
- 发布时间不可控(在 production 发布)
- 要求不停的集成(master 上集成)
GitLab Flow(有环境要求)模式除了两个主分支,还有环境如灰度:
- master分支负责持续集成
- pre-production分支负责验证灰度环境
- production分支负责正式发布
适用于:
- 需要通过测试环境验证才能发布
- 要求不停的集成(master 上集成)
GitLab Flow(有发布分支)模式则是由主分支和多个需要维护的版本分支同时存在:
- master分支负责持续集成
- version-stable各个版本的发布分支
适用于:
- 需要发布同时维护不同版本的项目