您现在的位置是:首页 >技术杂谈 >GIT-FLOW工作流网站首页技术杂谈
GIT-FLOW工作流
简介GIT-FLOW工作流
初识GIT
初识git版本管理
- 什么是git
git就是一个分布式版本管理工具,它可以在多台主机上保留有整个项目的所有版本。而相对的是集中式版本管理,比如svn,项目的所有版本都保留在远程的一台服务器上,需要时将远程的最新版本下载到本地进行修改,然后再提交到远程服务器上,每台维护版本的主机只能保留独立的版本。 - 什么是版本管理
版本就是在开发过程中的每一个项目阶段的项目状态,可以被理解为一个版本节点,而版本管理就是对这些版本进行统一的管理和维护,比如版本回退、获取文件历史版本、对比历史文件和当前文件的修改过程等功能。其中蕞重要的就是版本回退功能。 - git与github的关系
git是维护本地版本的工具,而github是作为分布式管理的远程中转站,你可以将本地的所有版本提交到github上,在网络上访问自己维护的项目版本。
初始化本地git仓库
-
创建并初始化本地仓库:git init 生成.git文件,是git版本管理的基础文件
-
git config --global core.quotepath false【解决文件名解码问题】 git config:【需要标明配置的范围】 --local 局部配置信息【当前目录内】 --global 全局配置信息【本用户】 --system 应用于整个计算机【所有用户】 --unset 删除配置信息 user.name 设置用户名 user.email 设置邮箱【影响提交信息】 alias.变量名 简化指令 例如:git config --global alias.ci commit git commit == git ci
-
密钥生成:
windows上生成密钥算法:ssh-keygen -t rsa -b 4096 -C "个人邮箱" linux上生成密钥算法:ssh-keygen -t ecdsa -b 521 -C "个人邮箱"
-
git clone
直接克隆别人的仓库到本地
开发应用工作流
简单工作流
流程:
- [git pull]
- do{
- 开发
- git add .
- git commit
- }while(开发没结束)
- git push
使用指令:
- git add [指定文件/.] 加入文件到暂存区
- git commit
git commit 将暂存区的数据形成一个新版本,并把此时的节点设为父节点,然后把当前分支指向新的提交节点,并写入版本注释
【工作区中未加入缓存区的数据,不会被记录进版本中】
-m '说明' 写入注释信息【-m 后面必须紧跟注释信息】
--amend git会使用与当前提交相同的父节点进行一次新提交,旧的提交会被取消
- git status 查看与当前HEAD所在版本对比的修改情况
[应用]
1. 修改文件名
mv b btest
git add .
git status
只是修改文件的名称,而并不改变blob文件,证明了文件名保存在tree中,而不在blob中
[意义]
1. 因此更改文件名后,提交该文件,可以很大程度减小git的容量开销
2. 不同版本中同文件名的文件,git认为他们都是同一个文件的不同修改的结果
3. 同名文件尽量存储不同意义的信息,如果一定要改变某文件的整体意义,
那么可能合并时,需要解决文件冲突,解决后可以使用
- git log
git log 查看版本信息,只显示HEAD之前的版本信息
【默认打印Merge信息】
--oneline【简短按行打印log信息】
--all 【显示'所有'分支的log(包括远端)】【一般情况,只显示所在分支向下的log】
--graph 【以绘制版本分支图的方式,显示log信息】
--decorate 【显示HEAD、分支、标记的名称等】
--merges 【只显示merge产生的节点版本】
--min-parents=n【设置查看最少合并分支数的merge信息】【默认为2】
[检索版本和文件]
--author="作者名(可正则)" 【只显示该'作者'的提交记录】
--grep="log注释(可正则)" 【在多版本中搜索'注释匹配'的log】
[--] filename 【查找版本中存在该'文件名'的log】
-S "文件内容" 【查找log中修改'文件内容'的log】
-n 显示最近的n条提交记录
git log -3
-p 打印所有版本的变更细节
git show 版本号 【展示单个版本号的具体提交信息】
[git diff]
git diff 同名文件差异对比
git diff [index -> work][源->目标]
git diff HEAD [HEAD -> work]
git diff 分支 [分支 -> work]
git diff 版本1 版本2 [版本1 -> 版本2]
-- 文件名 专门对比某文件的不同
(冲突行:diff中描述的是两个文件合并后的差异结果,空格开头的行代表源文件与目标文件没有差异,
以-开头的行,代表在源文件的基础上删除,以+开头,代表在源文件基础上添加)
【因此diff实际是用对比的方法,将对比结果表示为一个文件】
[diff --color -u]
不同名文件差异对比【类似git diff】【或者colordiff -u】
<since>...<until> 限制log搜索范围(since,until]
git log HEAD^^^...de9856a
案例:
git log --author="John Smith" -p hello.py
这将显示 John Smith 对 hello.py 文件所做的所有变更的完整比对。
git log --all --merges
查看所有分支线上合并数最少为2个父节点的所有版本节点
远端:
- 远程仓库中保存的分支版本结构
- 所有分支、标签,都独立存在于仓库中,互不影响
- 仓库中的commitID不因正常提交而随意更改【push -f除外】
- 远程仓库与本地仓库同步时,根据commitID进行叠加同步
- git remote
git remote 【设置远程仓库变量】
add 远程仓库变量 远程地址【添加】
rm 远程仓库变量 【删除】
-v 查看远程库变量】
prune 远程仓库变量 【删除本地中,远程已不存在的分支】
- git ls-remote
git ls-remote origin 查看远程现存的分支、标签等信息【联网查看】
- git clone
clone会将仓库中所有资源全部clone到本地
-b 分支【克隆指定分支到本地】
- git push
git push 远程仓库变量 【上传远程仓库】
branchName 推送本地指定分支进远程仓库【上传分支】【只允许ff合并】
--tags【推送所有标签】
tag名【推送指定tag】
--delete branchName/tagName 删除远程仓库分支/标签【本地对应的远程分支也会被删除】
-f 强制提交【改变仓库commitID】
-u 将提交分支与远程上传分支进行跟踪
- git fetch
[git fetch]:获取
git fetch origin 获取远程存在的所有分支
git fetch origin branchName 获取远程仓库的指定分支
-v fetch远程与本地版本对比的详细信息
- git pull
git pull == git fecth + git merge
一般情况下:
git config pull.rebase false # merge (the default strategy)【使用默认merge】
git config pull.rebase true # rebase
git config pull.ff only # fast-forward only
将origin/master拉入HEAD进行文件合并
【fetch其它仓库 == 一个仓库里同时存在多个仓库】
远程合作工作流
流程:
- fork合作项目到自己的github账户下
- git clone
- git checkout -b new_feather
- do{
- 开发
- github同步sync远程master
- git pull到本地
- git rebase
- }while(开发没结束)
- git push origin new_feather
- pull request、squash and merge
- 删除仓库中working分支、sync本地master、git remote prune origin 删除本地中远程没有的分支
使用指令:
- git checkout
git checkout 切换当前所在版本
[头结点切换]
git checkout [分支/版本]
工作区:非HEAD与目标版本的文件不发生任何变化,其余文件全部改变
暂存区:索引对当前HEAD所在版本的所有blob文件的指向全部删除(包括删除指向的文件),索引添加指向到目标版本
的对应blob文件上,并将新添加指向的所有文件拷贝到工作区中【索引部分指向的删除】
(目标版本文件,原版本独有文件,非二者文件)
[不受影响的文件]:
1. 索引中,不属于两个版本的文件
2. 未跟踪文件
git仓库:只移动HEAD到目标分支/版本【匿名分支】
-f 强制切换分支/强制执行标准操作【将完全执行如上操作】【默认中,两个版本的文件如果被修改会无法切换】
[应用]:
1. 暂存工作区,保留工作环境,无丢失的执行checkout
git stash -u
git checkout commitID切换
git stash pop
[git stash]
使用原因:
一般只有在commit版本状态后,书写的内容才被正式存入git仓库进行维护,否则切换版本后,
文件可能丢失,就需要一种暂存工作环境的方法--->stash
使用背景:
1. 写到一半,突然需要去另一个分支做维护,但是暂时又不想commit当前版本
2. 在当前所在版本下做实验,需要保存多个实验版本,但是又不需要commit
操作:
-u 可以储存未跟踪文件【untracked】
-a 可以储存.gitignore的文件【默认不保存未跟踪和.gitignore文件】
1. 简易使用方法
git stash -u 工作环境压入栈中,回到HEAD版本 // 保存环境
git stash pop 弹出最近一次工作环境并恢复 // 弹出环境
2. 复杂使用方法
git stash save '工作环境注释' // 保存环境
git stash list 显示stash栈表信息 // 查看所有环境信息
git stash pop 弹出并恢复最近一次提交的工作环境
git stash pop stash@{id} 弹出指定工作环境并恢复 // 恢复工作环境并删除
git stash apply stash@{id} 恢复指定工作环境,不删除栈中工作环境
git stash drop stash@{id} 指定删除栈中工作环境 // 恢复工作环境,可删除可不删除
git stash clear 清空储存
PS:
1. stash保存的是工作区和暂存区的工作环境,保存后,工作环境回到HEAD版本状态
2. 保存的stash工作环境,保存在当前版本位置上,而不是保存在HEAD/分支上
2. 恢复指定文件
git checkout HEAD -- filename
3. 获取历史版本文件
git checkout commitID -- filename 【HEAD是不移动的】
4. 撤销指定文件到指定版本
# 查看指定文件的历史版本
git log -- <filename>
# 回滚到指定commitID
git checkout <commitID> <filename>
[头结点分离]
git checkout 版本
切换过程,同上理
1. 只移动HEAD到目标提交上
2. 修改后的文件版本,在commit后,一般需要
git branch 分支名 生成一个新分支方便找回,
【否则该分支在HEAD切换后,除了reflog,否则无法再被找到】
[git branch]
git branch 分支名,在HEAD处创建分支【HEAD并不移动到分支上】
git branch -v 查看所有分支
git branch -vv 查看所有分支,包括远程分支
git branch -d 分支名 删除分支【不是删除版本】
git branch -D 分支名,强制删除分支
git branch -M XXX 更改HEAD所指分支的分支名为XXX
git branch 分支名 版本号 【指定版本处创建新分支】
git checkout -b 分支名 创建并切换分支【创建分支并移动到分支上】
- git worktree
[git worktree]:更灵活的工作环境保存的方法
add ../branch_file branchName
-b branchName ../branch_file 创建一个新分支,同时可以为他创建worktree文件
[描述]:
1. 仓库中HEAD所在分支,即被认为是worktree文件的工作分支
主仓库,也相当于一个worktree文件
2. worktree文件中的HEAD不能切换到有worktree文件的分支,
但可以切换到当前没有worktree文件的分支中进行开发
【worktree list中显示的分支】
3. [share]:
【worktree的分支们就像还是在一个文件中工作】
不同worktree下工作目录中的本地'更新',会在其他worktree中直接'同步共享'
list 显示当前所有worktree文件,和文件中HEAD所在分支
prune 删除不存在的worktree文件对该分支的限制【删除后才会解除限制】【分支回归】
-n 查看需要删除的worktree文件
- git rebase
[git rebase]:衍(重演)合
git rebase <目标基点>
描述:整理HEAD原始基点与HEAD之间的commit后,将整理好的commit,衍合到目标基点后
[HEAD原始基点]:
1. 当HEAD与目标基点同线(一条版本线;包括合并后的分支)时,HEAD递归如果找到base,则base为HEAD原始基点【一条线,能找到】
分支末尾:体现为,整理commit
分支中间:体现为,产生新分支
2. 当不同线时,以目标基点所在分支与HEAD所在分支交点处为HEAD原始基点【两条线】
base末尾:体现为,衍合
base中间:体现为,产生新分支
3. 当HEAD找不到原始基点时,体现为noop【no operation 无操作】 【一条线,找不到】
【例如:一条线时,HEAD在base前;HEAD向前递归找不到base】
将分支/HEAD移到base所在节点
案例:
git rebase HEAD^^ -i == 整理HEAD,HEAD^,以HEAD^^为base
git rebase -i 版本/分支名【交互式合并】
交互界面的TODO_LIST,更换顺序 == 更换重演顺序【默认从上到下为基点后第一次提交开始】
【整理合并顺序】
squash 将选中的版本,集成到前面的版本上进行提交【TODOLIST中,前面至少有一个提交】
提交的版本注释修改,可以自定义一个,也可以自己在三个中选一个
ps:
1. 处理冲突时,--ours是base所在分支的数据,--theirs是合并的分支所在的数据
2. 处理冲突结束后,使用git rebase --continue继续合并
【rebase的缺点】:
1. 可能造成版本提交混乱
由于rebase过于自由,如果revert的版本,可能被重新提交进主分支,造成版本混乱和疑惑
[git revert]:【重新生成节点的回退方式】
1. 撤销线性提交:
git revert <commit id>
将此id前的第一个提交和HEAD所在版本进行对比后,进行一次新的提交
-m 合并分支的序号 mergeID
配合git log --merges查看合并版本节点
-n 不自动提交
[应用]:
1. 线上开发时,直接reset会影响全局的hash,使用revert,
可以即取消错误版本造成的影响,又可以实现一次正常的线上提交
2. revert恢复
2. 可能造成时间线混乱,让人产生疑惑
rebase就是重演提交之间的过程,会改变commitID,但不会改变commit的时间,因此时间会让人疑惑
深入了解GIT
git实现版本管理的底层原理
-
git将区域分为:工作区、(索引)暂存区、git仓库
.git文件内包含了暂存区+版本区,其外为工作区 -
原理: 0. 暂存区实际就在git仓库中,只是在commit前没有构建commit连接不算入库 1. git 维护的是一个键值对数据库(key-value database): 根据文件的key,获取其数据value key:SHA1哈希码,40位的字符串 2. git object是git储存信息的最小单元 blob 文件内容 tree 目录结构、文件权限、文件名、blob的key commit 父commit、本次快照、提交者、提交信息、tree的key 3. 查看git结构的指令: 1. 查看暂存区(索引): git ls-files --stage 查看暂存区中的git object文件 2. 查看git object文件信息: git cat-file -t SHA1哈希码,查看文件的git Object类型 git cat-file -p SHA1哈希码,查看文件的内容 3. 直接查看文件转码SHA1哈希后的数值: git hash-object 文件名 【返回文件的key】
指令
-
git reset
git reset 删除版本节点/重置分支指令 【回退参数】 --soft 【修改程度最小】 工作区:所有文件都不发生改变 暂存区:索引不发生任何改变和移动 git仓库:HEAD和分支移动到目标版本上 git reset --mixed == git reset【默认回退方式】 工作区:所有文件都不发生改变 暂存区:之前索引的所有指向全部删除(并不删除指向的文件),索引移动到目标版本的对应blob文件上 git仓库:HEAD和分支移动到目标版本上 [应用]: 1. 删除git add添加到暂存区中的指定文件的索引指向【不删除暂存区中的blob文件】 git reset -- filename 【没有设置版本,因此HEAD和分支不动】 2. 删除所有新添加的索引指向 git reset HEAD --hard 【修改程度最大】 工作区:更改为目标版本的所有文件,之前存在的文件全部消失【未跟踪文件不会消失】 暂存区:之前索引的所有指向全部删除(包括删除指向的文件),索引移动到目标版本的对应blob文件上, 并将版本的所有文件拷贝到工作区中 git仓库:HEAD和分支移动到目标版本上 [应用]: 1. 彻底删除版本 git reset --hard 历史版本ID【如果不考虑重置功能,则一般不通过移动分支来切换版本】 git clean -df 删除所有未跟踪文件 git clean 删除未跟踪文件【不会影响到.gitignore 指定的未跟踪文件夹或文件】 -n 查看运行后会删除的文件信息【试运行】 -f 强制开启删除未跟踪文件功能【不包括文件】 说明clean.requireForce 配置选项设置为 false -d 可以删除未跟踪的目录 2. 彻底恢复HEAD所在版本 git reset --hard HEAD 【抛弃除了未跟踪文件外,所有文件的更改】 【移动方式】 HEAD后移:eg: git reset --hard HEAD^ 回退到上一个状态 HEAD前移:回到以前的节点: git reflog git reset --hard commitID [git reflog] 记录所有使版本库状态改变的操作【只记录在本地,不会提交到远端】
-
git tag
[打标签]:git tag 描述:标签指向commit,并且不能移动 [产生标签] git tag 标签名 【默认生成在HEAD所在版本】 git tag -a 标签名 -m '标签注释'【只在git show tag名称时显示注释】 git tag 标签名 commitId 指定commit创建标签 [操作] git tag 显示所有'标签名' git show tag名 显示tag的具体信息【包括注释和对应commit】 git tag -d 标签名 删除标签 git checkout 标签名 切换HEAD到标签 -f 强制修改同名标签【标签不允许同名】 [推送]:标签必须手动推送push时不自动带标签推送 git push origin --tags【推送所有标签】 git push origin 标签名 【推送指定标签】 git push origin --delete tag标签名【删除远程标签】
-
git blame
git blame 查看每行提交的作者 [应用] 1. 找到指定文件中某行内容的最早提交者 配合git log -- filename 查看指定文件出现的版本 git checkout commitID切换到对应版本 git blame 直接查看文件的提交作者
-
git bisect
git bisect 以二分的方式查找所有版本【一般用来查找代码出错的版本处在哪】 start term-new term-old 从old版本开始到new版本进行二分查询【必】 good 查验版本信息后,手动回复该版本ok,开始下一次二分 bad 找到出错版本/二分的最后一次结果默认为bad reset 退出bisect模式,HEAD回归上一次所在分支
-
git merge
[git merge合并分支] 合并 == 合并分支文件修改内容 + [提交生成新版本] [同一条版本线上] <快速合并ff>: 快进(fast-forward) git merge 目标版本/分支【HEAD所在旧分支 -> 新分支合并】【否则合并失败】 [应用]: github上在push时,就是采用ff方式合并提交的分支结果,并且github上的提交,只支持ff的提交方式 <禁止快速合并,强制分支形式合并>: git merge --no-ff 目标版本/分支【如果可以ff,则优先ff,因此需要--no-ff取消ff】 【本质是将更远的节点拷贝一份模拟一个分支合并的形式】 [应用]: 让分支突兀出来,保留分支,做其他处理 [分开的两条版本线] <分支合并>: git merge 目标版本/分支 将目标版本拉向HEAD所在版本进行内容合并 [非正常合并---冲突文件] 冲突原因:不同版本对'同一文件'同一行进行不同修改 冲突解决:进入冲突合并状态【完成合并和合并前的中间态】 git会将所有冲突文件标记出来等你手动修改【git不会自动帮你做决策,因此保证合并的正确性】 git merge --abort 放弃合并 [merge的合并策略] git merge -s 合并策略 合并的分支. <三方合并> 区别于二方合并 <Recursive合并> 两个分支合并时的默认合并策略 递归寻找路径最短的唯一共同祖先节点,然后以其为base节点进行递归三方合并 【本质上,最终真正被三方合并拿来合并的节点,只有合并节点的末端和base】 <Octopus多分支合并> 两个以上分支合并时的默认合并策略 一旦出现文件冲突,合并立马自动结束 <Ours & Theirs> 分支选择策略 使用场景: 比如现在要实现同一功能,你同时尝试了两个方案,分别在分支 dev1 和 dev2 上, 最后经过测试你选用了 dev2 这个方案。但你不想丢弃 dev1 的这样一个尝试, 想用一种方法表明是在两个分支中选择了其中一个分支的实验结果作为最终结果, 这个时候你就可以在 dev2 分支中执行git merge -s ours dev1。 【本质,就是将选择的ours分支的最终结果拷贝了一次,类似于禁止快速合并--no-ff】 【theris 分支合并策略已经被删除了,这种策略是危险的,使用theris合并,相当于直接丢弃了另一个分支的所有内容】 [冲突解决中的ours & theirs]: git checkout --ours / git checkout --theris 文件名【直接选择某方文件,而不是手动解决冲突】
-
git cherry-pick
[git cherry-pick]:
摘取版本节点接在HEAD所在节点后
[单选]
git cherry-pick 版本号/分支
[多选]
git cherry-pick 版本1 版本2 [...]
[范围选]
git cherry-pick 版本1^..版本2
【范围中的版本是(版本1^,版本2] == [版本1,版本2]】
执行结束使用: git cherry-pick --continue继续
-
git submodule
git submodule【一个仓库下,存在的多个子仓库】 add [本仓库第一次添加库子模块] 1. 添加后生成.gitmodules文件,用来存储所有子模块仓库的地址 2. 初始化子模块仓库所有文件 update --init --recursive【重新获取子模块库】 [clone本仓库填充库子模块]:clone仓库时,不会clone子模块的内容 --remote [更新库子模块]
对于我的原创文章,如果读者有任何疑问都欢迎在评论区里留言,我看到后会尽快给大家回复?
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。