1. Git多人协作开发流程图
1.1 processOn默认的模板
1.2 改造之后
https://www.processon.com/view/link/64ccaf56a433c931b2f9428a
访问密码:512I
① 总流程图
② feat分支(功能/需求 分支)流程
③ bugfix分支(紧急补丁分支)流程
④ 回滚
上线后有严重问题,短时间内无法修复,需要回滚 -- 以下解决方案是个人想法
若需要全部回滚,则直接用上一个版本的快照重新发布,后续再按照【② feat分支(功能/需求 分支)流程】的步骤将最新修改逐步合并到各个分支,再上线
若本次上线了多个功能,而需要紧急回滚其中一个功能(都在feat-xxx)上,则处理流程与【③ bugfix分支(紧急补丁分支)流程】类似,如下图
2. 简略的基础
1.管理工具sourceTree https://www.sourcetreeapp.com/ 省下了敲命令的麻烦,SourceTree本身还是通过Git命令来执行任何操作
2.设置用户名、邮箱(可以针对全局,也可以针当前工程)
3.fetch、merge、pull的含义(pull = fetch + merge)
4.【commit id 版本号】执行命令时版本号没必要写全,前几位就可以了。HEAD指向的版本就是当前版本
5.工作区和暂存区:工作区和暂存区 - 廖雪峰的官方网站。对于所有分支而言, 工作区和暂存区是公共的。
6.ReadMe文件
7.gitignore文件。把要忽略的文件名(或者路径)填进去,Git就会自动忽略这些文件。
8.Git比其他版本控制系统设计得优秀的原因:Git跟踪并管理的是修改,而非文件
3. 总结操作流程
3.1 创建项目
第一步:gitlab页面创建空项目
此步骤 由管理者建项目
第二步方式A:将项目clone到本地
开发人员加入这个项目(权限),将空项目clone到本地,在该项目的基础上直接进行开发。
git clone 项目url
无论使用ssh还是http,都要执行clone命令,IDEA中也有执行clone的界面操作
方式一:命令行
git clone http://项目克隆地址
方式二:IDEA
第二步方式B:本地根据模板创建项目,再关联远程
在本地根据模板创建项目,执行命令git init
初始化为git仓库,再与远程仓库进行关联,将模板生成的代码push到远程仓库。
本地根据模板创建项目
此处勾选了【Create Git repository】,之后就不用使用命令行【git init】来初始化了
勾选一些需要的组件,此处演示仅勾选了Lombok。
此时模板为我们生成了一些文件,但此时,本地仓库没有关联远程仓库。
但此时执行git branch -a是看不到master分支的,原因:
当 git init 初始化一个空的 Git 仓库时,它不会自动创建指向第一次提交的主分支(即 master 分支)。
因此,当你第一次执行 git commit 命令提交代码之后,master 分支才会被创建。此时再执行 git branch -a 就可以看到 master 分支了。
本地仓库关联远程仓库
建立本地仓库和远程仓库关系
shell
git remote add origin http://xxxxxxxxx.git
拉取远程仓库代码
shell
git pull origin master --allow-unrelated-histories
--allow-unrelated-histories,表示允许不相干的两个仓库合并,因为我们之前两个仓库是完全独立的,所以直接是没办法pull下来,需要加上该参数才行
此时能看到远程仓库初始的commit添加了README.md文件。
提交模板生成的代码
总结第二步的方式AB
以A的步骤,手动把模板生成的代码copy到当前项目路径,再进行add、commit、push操作,效果与B一样。
但B的作用主要是将已有的项目(含有一些git提交历史记录)推送到新的空仓库。
3.2 创建环境分支
现有的master对应生产环境,
若有dev环境,就创建对应的dev分支,
若有uat环境,就创建对应的uat分支。
创建分支可使用命令行、IDEA界面、远程仓库管理页面。
3.2.1方式一:在页面创建,fetch到本地
此处下拉可以选择tag(若有的话)。
使用IDEA中的fetch按钮,相当于执行命令【git fetch origin】
可以在本地看到,远程有dev分支。
3.2.2方式二:在本地创建,push到远程仓库
在最新master的位置创建分支,相当于执行命令【git checkout -b uat 版本号】
此时uat分支只在本地存在,远程仓库没有。
所以我们push到远程仓库
此push操作相当于执行命令【git push origin】
可以看到远程仓库也有uat分支了
3.3 创建功能分支
与【3.2 创建环境分支】类似,此时开发人员要开发一个【功能/需求/任务】,步骤如下:
先git pull
,更新整个项目。
基于最新master(或者说是最新发布版本的tag)创建功能分支。功能分支命名举例:feat-xxx
其中"xxx"可以是敏捷相关管理系统里的【需求编号/子需求编号/任务编号】,个人认为尽量以最小单位的任务编号命名,也可以是自定义的需求功能首字母缩写,项目内部自己规定
在此功能分支上进行开发,及时push到远程仓库。
3.4 功能分支合并到dev分支
现状:有任务编号【123】,已基于最新master(或者最新版本的tag)新建功能分支【feat-123】,并将所做修改push到【origin/feat-123】
方式一:开发者自己本地合并
每个开发者自己本地合并,在命令行、IDEA中就可以操作
① 【git pull】(此时用fetch也行)更新整个项目
【pull = fetch + merge】,git执行完fetch之后发现,当前feat-123分支,本地与远程在同一个位置,就没再进行将 origin/feat-123 merge到 本地feat-123 的操作
②切换到最新origin/dev分支,此时本地dev与远程dev在同一个位置。
shell
git checkout -B dev origin/dev
注意此处checkout的是origin/dev,实际上执行的命令是【git checkout -B dev origin/dev】,
-B参数 (force create branch):如果已经存在一个名为new_branch的分支,则使用该参数意味着强制创建分支。例如,git checkout -B new_branch表示新建一个名为new_branch的分支,如果该分支已经存在,则强制覆盖该分支。
由于-B参数,此时我们本地dev分支会跟上最新origin/dev,与其处于同一个位置。
③将功能分支【feat-xxx】merge到dev分支
由于本次feat-123所处的commit与dev分支没有分岔,此时这样merge会使用Fast forward模式,不推荐
Fast forward模式导致:功能分支merge到dev分支时,dev分支直接跟了上来,没有产生新的merge。
此时推荐这样merge:
④将dev分支push到远程origin/dev
方式一补充:个人不建议的方式
切换到dev分支,把远程功能分支(origin/feat-xxx)pull到dev分支。Pull into 'dev' Using Merge
由于【pull = fetch + merge】,
此操作会先fetch,获取所有远程分支的位置,再把远程功能分支merge到dev分支。
最后push dev分支
但我个人不建议,因为会出现这个情况:
当远程dev领先于本地dev,此时将远程功能分支(origin/feat-xxx)pull到dev分支,会导致【本地dev分支】与【远程origin/dev】出现分叉,在push时会被驳回,需要多一个merge。
方式二:在页面上提交合并请求
在页面上提交merge请求,将功能分支合并到dev分支。
我们先回到【方式一】操作前的这个状态,本地与远程dev分支都处于【dc733298】这个commit节点。
下面演示如何在页面上提交合并请求
【merge】按钮需要有merge权限的账户才可以操作。
但是如果有冲突的话,还是要回到【3.4 功能分支合并到dev分支/方式一】,在开发工具中进行合并解决冲突更方便。
此时我们在本地pull(fetch也行),就能看到最新远程dev分支了。
冲突
若方式二在页面上功能分支【feat-xxx】合并到【dev】分支时提示冲突,
回到【3.4功能分支合并到dev分支/方式一】中使用本地merge(页面上也能操作解决冲突,但不方便),切换到dev,将功能分支merge到dev,直接进行冲突解决。
3.5 功能分支合并到master
与3.4操作类似,只是【target branch】由dev变为master
3.6 上线后对最新master标记tag,表明本次版本
标记tag详见【4.11 git tag】
4. Git操作
4.1 git checkout
可以切换分支。
创建分支:可以用命令git checkout -b feat-xxx
,可以在远程仓库页面上直接创建再pull到本地
关于git checkout HEAD
,等同于IDEA中直接对某文件rollback
4.2 git status
IDEA中可以直接查看
红色-未被git跟踪
绿色-新增文件
蓝色-修改
在IDEA中操作commit时会帮我们自动add
IDEA中还可以创建changelist把所做的改动加到不同的组里。
4.3 git log
① 命令行查看commit信息
git log --pretty=oneline
查看提交历史
② 命令行查看提交历史路线图
git log --graph --pretty=oneline --abbrev-commit
③ gitlab页面上查看提交历史路线图
项目/Repository/Graph
④ IDEA中查看提交历史路线图
标签颜色含义
黄-HEAD,本项目只关联一个远程仓库时,只会存在一个黄色标签,表示当前处在哪个commit
绿-本地分支
紫-远程分支,会显示【origin/分支名】,origin是默认的远程仓库的名字
⑤ 从log中查找【某文件】里关于【string】这个字符串内容的增删,可精确到具体的commit
git log -S <string> path/to/file
例:
shell
git log -S 'xxxxxxxxx' -- src/main/java/com/xxxxxxxxx.java
4.4 git revert
1.回滚某个commit
git revert commitId
2.回滚某个merge请求,可以在页面上操作,默认会创建一个新的revert-xxx分支,再合并到指定的分支上。
4.5 git reset
回退到上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,可以写成HEAD~100
Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是改变HEAD指向的commit,然后顺便把工作区的文件更新了
4种模式,我常用Mixed和Hard(但是hard慎用,会清空本地所有未提交的修改)
git reset --soft commitId
IDEA中使用undo commit也可以达到同样效果(回退到上一个commitId)
4.6 git reflog
用来记录你的每一次命令
这样就可以知道想找的版本号(commit id)
4.7 git rebase
① git rebase
命令把分叉的提交变成直线 -- 不推荐使用,分支历史不清晰,且如果使用了强制推送可能会影响别人。
② 合并两个commit -- 可以在自己的功能分支合并到dev分支前使用
相关命令笔记:
shell
1.
git rebase -i HEAD~2 -- 参数-i是不需要合并的版本号(commit的hash值)
2.
进入编辑模式后将pick修改成fixup或者squash等关键字。
p,pick 不对该commit做任何处理
r,reward 保留该commit,但是修改提交信息
e,edit 保留该commit,但是rebase时会暂停,允许你修改这个commit
s,squash(保留应用,合并commit描述)保留该commit,但是会将当前commit与上一个commit合并
f,fixup(保留应用,丢弃commit描述)与squash相同,但不会保存当前commit的提交信息
x,exec 执行其他shell命令
d,drop 删除该commit
注意不能将第一条也就是最近的一条commit给pickup或者squash,这样它就找不到之前的commit进行合并,然后就会报出错误
3.
commit message的编辑界面,输入commit描述信息
如果遇到错误 执行取消命令
git rebase --abort -- 取消这次rebase
③ rebase onto 最新分支-- 可以在自己的功能分支合并到dev分支前使用,效果类似于"基于最新的master分支重新做一遍已经提交的代码"
4.8 git cherry-pick
能复制一个特定的提交到当前分支
4.9 git remote
远程库的名字就是origin,这是Git默认的叫法,也可以改成别的,但是origin这个名字一看就知道是远程库。
查看本地仓库与远程仓库的关联详情
git remote -v
可以同一个项目关联多个远程库
https://www.liaoxuefeng.com/wiki/896043488029600/1163625339727712
切换远程仓库步骤参考:
shell
git remote -v // 查看本地仓库与远程仓库的关联详情
git remote rm [远程仓库名] // 解除与远程仓库的关联
git remote add [远程仓库名] "[远程仓库地址]" // 将本地仓库与远程仓库关联
4.10 git stash
可以把当前工作现场"储藏"起来,等以后恢复现场后继续工作
git stash save 这是本地临时存放起来的未提交的修改
取出时(unstash)
git stash apply stash@{0}
相关命令笔记:
shell
git stash save "save message" // 执行存储时,添加备注,方便查找,只有git stash 也要可以的,但查找时不方便识别。
git stash list // 查看stash了哪些存储
git stash show // 显示做了哪些改动,默认show第一个存储,如果要显示其他存贮,后面加stash@{$num}
git stash show -p // 显示第一个存储的改动,如果想显示其他存存储,命令:git stash show stash@{$num} -p
git stash apply // 应用某个存储,但不会把存储从存储列表中删除,默认使用第一个存储,即stash@{0},可更改num
git stash pop // 应用某个存储,把存储从存储列表中删除,默认为第一个stash,即stash@{0}可更改num
git stash drop stash@{$num} // 丢弃stash@{$num}存储,从列表中删除这个存储
git stash clear // 删除所有缓存的stash
4.11 git tag
标签
https://www.liaoxuefeng.com/wiki/896043488029600/900788941487552
IDEA中创建tag好像不支持添加说明
在页面上或者用命令行操作,可以创建带有说明的tag。git tag -a v1.4 -m 'version 1.4'
标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签
标签是指向commit的死指针,分支是指向commit的活指针
5. 建议的习惯
5.1 commit信息
①commit信息是要写明白的,讲明白这次提交是做了哪些事。
之前某系统有人提交过二十几个commit全写的一样的字,合并代码遇到冲突时很难知道TA某次commit所做修改的目的。
②另外,如果改动较多,建议把不同的步骤做多个commit,不要都写在一个commit里,
不便于别人查看(抄)你修改的代码,自己回顾的时候也不方便
如果出问题,也不便于revert。虽然可以按照单个文件回退那次的修改,但若单个文件中做了多个任务的修改,还是需要查看代码逐行确认。
下图是,我某次commit的内容包含了四种不同内容,此刻我是反例。当然该项目未上线,处于快速开发的阶段,对git使用没有过多要求。
如果未来可能进行【复盘】来向别人分享写过的优秀代码,从每个commit对比文件修改前后的内容也是比较方便的。
③最后,个人习惯,建议每个commit都以【分支的尾缀】开头,比如feat-123分支,我的commit信息会写【123 修改某逻辑】。
还有的项目规定以类似这样的形式开头:feat[123]、feat-123、bugfix[123]、bugfix-123,可以自行规定。
5.2 一个分支不要被多个人使用
多个人都用同一个分支(甚至都用master)有弊端,就是没有利用好git分支管理的优势,就像SVN一样在使用。
另外,共用同一个分支容易push失败,以及pull时merge过来可能有冲突。因为该分支在远程仓库可能有别人push的内容,领先于你本地。
5.3 查看提交人
git中可以查看每一行是谁写的、
git的user.name、user.email都是自己定义的,如果项目没有明确规范要求,不建议用xxx-yyy(yyy是每个人都一样的公共后缀),会显示yyy,大家都是yyy的话,不直观,得去历史记录里看具体的提交人、
5.4 commit和push之前确认一下自己的修改
commit和push之前确认一下自己的修改,以免提交了本不改提交的(比如修改特定的本地配置,是不是导入了包后来没用到等等问题)
可以创建另一个changelist单独管理暂时不需提交的修改。
5.5 不使用Fast forward模式
通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
--no-ff
5.6 下班及时push代码
每天下班push,相当于备份保护自己写的代码,没下班时也可以及时push。硬件是不完全可靠的,多个备份多个保障,不要写代码写好长时间(甚至以周以月为单位)一直留在自己本地不push。
哪怕没写完,有些地方编译都不通过,也可以push,因为在自己的分支上且没合并到其他分支,不影响别人。这种情况,第二天再打开继续工作时有两种处理方式:
-
reset(使用--soft模式,IDEA中可使用"undo commit")回到commit前的状态,继续开发,再commit,再强制push
-
不reset,继续commit最新的开发,再使用rebase合并两个commit为一个
若在家加班换了电脑完成了一部分开发并push,第二天又用回办公室的电脑,可以按如下步骤处理:
-
删除本地功能分支feat-xxx
-
git pull
-
checkout 最新远程feat-xxx分支
补充:强制Push(git push -f
)
5.7 定期删除功能分支
有的项目要求功能分支永不删除,
但我认为上线后功能分支留着没用,若保留,日积月累,分支数量将无穷增长,翻找分支变得不方便。廖雪峰官网所介绍的流程也是要删掉功能分支的。
且如果没使用Fast forward模式,merge历史记录里也是能搜到这个分支名的,若像我的习惯在commit信息以【分支尾缀】开头,更是能搜到了,所以没必要保留已上线的功能分支。
也可以每年清理一次太旧的功能分支。