Git 使用指南
Git 是目前世界上最先进的 分布式 版本控制系统,本文详细介绍了 Git 在单人模式和多人协作下的基本概念、使用方式和常用指令,最后对比了几种最流行的 Git 协同模型,可以更高效地进行团队开发。
版本控制系统
SVN 集中式版本控制系统
- 版本库放在中央服务器
- 必须联网才能工作
- 服务器磁盘故障存在数据丢失风险
Git 分布式版本控制系统
- 没有中央服务器,每台个人PC都有一个完整的版本库
- 工作时无需联网
- 通过把各自的修改推给对方进行协作
- 服务器数据丢失可用任意一个代码仓库恢复,宕机期间也可以提交代码到本地仓库
- 安全性强,Git管理的每一个文件、目录、提交等都拥有一个SHA-1哈希值
Git 独奏
基本概念
暂存区
- 暂存区一般称为stage或index,只有添加到暂存区的文件才可以被提交。
- 暂存区保存在
./img/Git使用指南/index
文件中,是一个包含文件索引的目录树,有类似工作区的目录结构,其中记录了文件名、文件的状态信息等。 - 具体的文件内容保存在git对象库
./img/Git使用指南/objects
目录中,文件索引建立了文件和对象库中对象实体之间的对应关系。
HEAD
可以把
HEAD
理解成一个指针,HEAD
指针通常指向当前所在分支的分支指针,分支指针总是指向当前分支的最新提交,即通常情况下HEAD
指针总是指向了当前分支的最新提交(通过分支指针间接指向)。HEAD
指针和分支指针的当前指向,分别保存在./img/Git使用指南/HEAD
文件和./img/Git使用指南/refs/heads/<branch>
文件中。在git中,可以在./img/Git使用指南/refs
目录下找到一系列含有SHA-1值的文件,这类文件也被称为“引用” (refs或references)。$ cat ./img/Git使用指南/HEAD
ref: refs/heads/test$ cat ./img/Git使用指南/refs/heads/test
6f396a68315694be320949fba3f6a57d5e1c318f分离头指针(detached HEAD),即
HEAD
指针没有指向分支指针,而是直接指向了某个具体的提交。一般用来快速地基于某个提交进行一些实验或者测试,如果对结果满意就保留,否则就丢弃。在写法上,通常用
HEAD
表示当前版本,HEAD^
是上一个版本,HEAD^^
是上上一个版本,上100个版本可以写作HEAD~100
。
创建仓库
- git init:初始化仓库
- git clone:克隆远程仓库
1 |
|
- git config:修改配置文件(优先级:local > global > system)
初始化结果:生成.git文件(本地版本库),用户名和邮件地址配置成功(不做校验)
文件变更
- git add:添加文件到暂存区
1 |
|
- git commit:提交暂存区文件到本地仓库
1 |
|
- git status:查看工作区和暂存区之间的文件变更状态
1 |
|
- git diff:比较文件内容差异
1 |
|
当执行
git status
或git diff
命令扫描工作区改动的时候,git会先依据./img/Git使用指南/index
文件中记录的(已跟踪文件的)时间戳、长度等信息判断工作区文件是否改变。如果文件时间戳发生改变,则文件内容可能被修改了,此时再打开文件,对比文件内容,判断文件是否真正被更改。这也是Git高效的因素之一。
- git rm:删除文件,并自动变更暂存区
1 |
|
- git ls-files:查看工作区和暂存区的文件信息
1 |
|
版本切换
- git log:查看提交历史(显示当前
HEAD
之前的提交历史)
1 |
|
- git reflog:查看命令历史
1 |
|
- git reset:重置
1 |
|
reset实质:重置引用(改变
HEAD
指针所指向的分支指针指向,HEAD
指针始终指向当前分支指针)
- git revert:版本回滚
1 |
|
reset和revert的区别:git reset是回到某次commit,在本次commit之后的修改都会被删除;git revert是在最近一次commit之后生成一个新的commit,之前的所有commit都会被保留。
Git 和声
分支管理
- git branch:分支管理
1 |
|
- git checkout:检出
1 |
|
checkout实质:重置
HEAD
远程同步
- git remote:操作远程仓库
1 |
|
远程分支通常写作
远程主机名/分支名
,如origin/master
,保存在./img/Git使用指南/refs/remotes/origin
目录下
- git fetch:获取远程仓库中的最新版本到本地
1 |
|
- git pull:获取远程仓库中的最新版本并merge到本地
1 |
|
git pull = git fetch + git merge
- git push:推送本地修改到远程仓库
1 |
|
合并分支
- git merge:合并分支
1 |
|
- git rebase:变基(衍合分支)
1 |
|
merge和rebase的区别:
git merge会将两个分支的最新一次提交进行合并,发生冲突并解决冲突后,执行
git add
和git commit
,最终会产生一个新的commit。合并的结果为非线性,但只用解决一次冲突。(快进式合并的合并结果为线性,且不产生任何新的commit)git rebase会把当前分支的commit放到公共分支的最后面(变基),发生冲突并解决冲突后,执行
git add
和git rebase --continue
,不会产生额外的commit,会改变最近一次commit的hash值。合并的结果呈现为线性,但看不到完整的历史脉络(不要在公共分支使用rebase,否则容易破坏其他人的commit记录),同时rebase的冲突需要一个个解决(把commit打散成patch),一次rebase可能要解决多次冲突。
- git cherry-pick:择优挑选
1 |
|
使用merge合并另一个分支的所有变动,使用cherry-pick合并任意分支中的部分提交。
冲突处理
Git 进阶
其他指令
- git tag:标签管理
1 |
|
tag实际是一个指向commit的引用,保存在
./img/Git使用指南/refs/tags/
目录下,可以使用git checkout <tagname>
检出
- git stage:暂存
1 |
|
- git blame:文件追溯
1 |
|
- git bisect:二分查找
1 |
|
文件忽略
- 使用
.gitignore
忽略不希望添加到版本库的文件。 .gitignore
文件可以放在任何目录。.gitignore
文件模板- 忽略语法:
.gitignore
文件中的空行或者以井号(#)开始的行会被忽略。- 可以使用通配符:星号
*
代表任意多字符,问号?
代表一个字符,方括号[abc]
代表可选字符。 - 如果名称的最前面是一个路径分隔符
/
,表明要忽略的文件在此目录下,而非子目录的文件。 - 如果名称的最后面是一个路径分隔符
/
,表明要忽略的是整个目录,不忽略同名文件。 - 通过在名称的最前面添加一个感叹号
!
,代表不忽略。
1 |
|
常用指令速查
Git 协同模型
Atlassian:什么是成功的Git Workflow?
在评估团队的工作流程时,最重要的是要考虑团队的文化。工作流程应该能够提高团队工作的效率,而不是成为限制生产力的负担。评估Git Workflow时应考虑以下几点:
- 这个Workflow是否与团队规模相匹配?
- 使用这个Workflow是否容易撤销失误和修复错误?
- 使用这个Workflow是否会给团队带来新的不必要的认知开销?
Centralized Workflow
工作模式
- 管理员初始化中央存储库(裸存储库) —
git init --bare
- 开发人员从远程仓库克隆工程到本地仓库 —
git clone
- 在本地仓库编辑文件和提交更新 —
git add
和git commit
- Fetch远程仓库已更新的commit到本地仓库,并rebase到已更新的commit的上面 —
git fetch
和git rebase
或git pull --rebase
- Push本地master分支到远程master分支 —
git push
处理冲突
- 开发人员在执行第四步时,本地提交与远程提交可能会发生冲突,git会暂停rebase过程,需要使用
git status
和git add
来手动解决冲突。
优点
- 适合从SVN过渡到分布式版本控制系统,无需改变已有的工作流程和团队协作方式。
- 简单、直接,完美的线性提交记录。
- 适合小型团队。
Feature Branch Workflow
Feature Branch Workflow是对Centralized Workflow的逻辑扩展,其主要思想就是在开发每个功能时都应该创建一个独立的分支而不在master分支中进行。由于每个分支是独立且互不影响,这就意味着master分支中不会包含broken code,对持续集成环境很有帮助。
工作模式
- 仍然使用远程仓库和master分支记录官方工程
- 开发者为每个功能或问题创建一个单独的feature分支 —
git checkout -b
- 在本地仓库编辑文件和提交更新 —
git add
和git commit
- 将feature分支推送到远程仓库 —
git push
- 发送pull request来请求管理员是否将feature分支(远程)合并到master分支(远程)
Pull Request
- 开发者当完成一项功能后,不会立即将其合并到master分支,而是发起一个pull request请求。
- 其他的团队成员接收到请求,可以审查、讨论和修改代码(Code Review)。
- 项目管理员合并新增的feature分支到master分支,关闭pull request。
优点
- 每个功能都创建一个独立的分支,易于持续集成环境。
- Pull Request机制。
- 是一种非常灵活的开发方式。
TrunkBased Workflow
Trunk based development是Paul Hammant在2013年提出的模型,是SVN的基本开发模式。TBD模型由单个master分支(trunk)和许多release分支组成,所有开发都在trunk上进行,使用衍生出的release分支交付。
工作模式
- 在trunk分支开发,开发完成或hotfix后衍生出release分支发布。
- release分支不可修改,每一次更新,都有对应的版本号标签。
小规模团队:直接在trunk上进行开发。
大规模团队:从trunk衍生出短期的feature分支进行开发,开发完成后合并回trunk。
优点
模型简单
易于持续集成
GitFlow Workflow
GitFlow Workflow由Vincent Driessen在2010年首次提出并广受欢迎,它定义了围绕项目生命周期设计的严格分支模型。核心思想与Feature Branch Workflow类似,但是给特定的分支赋予了非常具体的角色,并定义它们应如何以及何时进行交互。
具体而言有四种分支:
- Main Branches
- Feature Branches
- Release Branches
- Hotfix Branches
工作模式
Main Branches
- master分支:用来保存正式的、可在生产环境中部署的代码,每一次更新,都有对应的版本号标签。
- develop分支:从master分支派生,是每次迭代版本时的共有开发分支。
Feature Branches
- feature分支:用来开发一项新的软件功能。
- develop分支可以同时衍生出多个feature分支。
- 当一个feature分支开发完成后,将这个分支上的代码变更合并至develop分支。
- feature分支应该永远不与master分支直接交互。
Release Branches
- release分支:用来存放准备发布的代码。
- 当develop分支开发完成后,可以从develop衍生出一个release分支。
- release分支中不应该添加任何新功能,应仅包含测试、错误修复、文档生成及其他面向发布的任务。
- 测试中出现的bug,统一在release分支下进行修改,并推送至远程分支。
- 修改内容必须合并回develop分支,上线时从release分支合并到master分支。
- 合并后的master应该被标记一个新的版本号。
Hotfix Branches
- Hotfix分支:用来快速给已发布产品修复bug或微调功能。
- 从master分支直接衍生出来。
- 完成bug修复后,合并至master分支以及develop分支。
- 合并后的master应该被标记一个新的版本号。
完整流程
优点
- 为管理大型项目提供了一个强大的框架,可用于大规模的团队协作场景。
- 非常适合有计划发布周期的项目,可帮助实施持续交付下的DevOps最佳实践。
使用场景
企业级开发,大型团队,敏捷要求相对较低(分支存活时间长短不一、合并繁琐、依赖管理)。
AoneFlow Workflow
AoneFlow只使用三种分支类型:master分支、feature分支、release分支,以及三条基本规则。
工作模式
规则一:开始工作前,从master分支创建feature分支。
AoneFlow的feature分支基本借鉴GitFlow,每当开始一项新的工作时,从master分支衍生出一条feature分支(通常以feature/
前缀命名),然后在这个分支上提交代码修改。每个工作项对应一个feature分支,所有的修改都不允许直接提交到master分支。
- master分支长期存在(项目的整个生命周期),由项目主要负责人管理。
- feature分支作为临时分支,用于开发的具体功能特性或修复bug,在功能完成后删除。
规则二:通过合并feature分支,形成release分支。
从master分支上衍生出一条release分支(通常以release/
前缀命名),将所有本次要集成或发布的feature分支依次合并过去。
- GitFlow:feature -> develop -> release
- TrunkBased:master -> release
release分支的用途可以很灵活,通常将每条release分支与具体的环境相对应,比如release/test对应测试环境,release/prod对应线上正式环境等等。
- release分支既可以为长期分支也可以为短期分支(存在于一个或者多个版本之间),由测试负责人管理。
规则三:发布到线上正式环境后,合并相应的release分支到master分支,在master分支上添加标签,同时删除该release分支关联的feature分支。
当一条release分支完成线上正式环境的部署后,为了避免在代码仓库里堆积大量feature分支,还应该清理掉已经上线部分的feature分支。与GitFlow相似,master分支上的最新版本始终与线上版本一致,如果要回溯历史版本,只需在master分支上找到相应的版本标签即可。
其他:对于hotfix,可以创建一条新的release分支,对应线上环境。
示例
1 |
|
优点
- 每个功能都创建一个独立的分支,易于持续集成环境。
- release分支的feature组成是动态的,易于调整需求或增删功能。
- release分支之间是松耦合的,可以有多个集成环境分别进行不同的feature组合的集成测试。
Forking Workflow
Forking Workflow与以上讨论的工作流程不同,它不是多个开发者共享一个远程仓库,而是每个开发者都拥有一个独立的服务端存储库。也就是说每个contributor都有两个仓库:本地私有的仓库和远程共享的仓库。
Forking Workflow这种工作流主要好处就是每个开发者都拥有自己的远程仓库,可以将提交的commits推送到自己的远程仓库,但只有工程维护者才有权限push提交的commits到官方仓库,其他开发者在没有授权的情况下不能push。Github很多开源项目都是采用Forking Workflow工作流。
工作模式
- 项目创建者在服务器上有一个官方的存储仓库。
- 开发者fork官方仓库来创建它的拷贝,然后存放在自己的服务器上。
- 当开发者准备好发布本地的commit时,他们push commit到他们自己的公共仓库。
- 在自己的公共仓库发送一个pull request到官方仓库。
- 维护者pull贡献者的commit到他自己的本地仓库。
- 审查代码确保它不会破坏工程,合并它到本地仓库的master分支。
- push master分支到服务器上的官方仓库。
- 其他开发者应同步官方仓库。
优点
- 便于多人协作和独立开发。
- 是开源项目的理想工作流程。
参考资料
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!