温情提示信息: 这不是一则详尽如是说大部份 git 命令用语的该文,责任编辑意在 如是说 Git 的核心理念结构设计,希望能协助听众认知基本概念和结构设计微观上的科学知识,从而能随心所欲地写作大部份 git 命令的文件格式和化解邂逅的难题
对于Git,不少人特别是初学者都处于 TNUMBERV12V4 的难堪期。
他们基本只会一招:
接著, 递交pull request / merge request,等候副组长 merge 就谁知了。
倘若是小学生或者自己的项目,那可能将更随便,间接
Git 只需要会这一招吗?Git 如此单纯吗?
– 标准答案毫无疑问是驳斥的!
只会一招,你能搞掂上面的情况吗?
你被重新分配到了2个分立的各项任务,在完成各项任务一的过程中由于这类因素中止合作开发了(比如说合作开发环境的某一伺服器有难题,先天就可以修通),你不得已去做各项任务二。
难题:那么各项任务四十里写到三分之一的标识符是不是留存?难道这2个各项任务是互相分立的,各项任务二的标识符能在各项任务一的标识符基础上竭尽全力往上写吗?
你正在合作开发各项任务二的这时候,同僚化解了一个邻近地区合作开发的操控性难题,且他的标识符早已被 merge 进了主组成部分(倘若你是 后端玩者,能假定 同僚大幅强化了 npm run start 和 webpack hot-deploy 的速率)。
难题:倘若你能用上同僚强化过的这段标识符,你的邻近地区合作开发会大幅提升工作效率。这时你该是不是办?间接 pull 吗?
你在合作开发各项任务二的这时候,早已写了非常多的标识符,但突然被通告终止各项任务二(倘若这是个 新优点,产品副经理决定中长期时间不来了,但今后可能将还会竭尽全力合作开发 )
难题:是不是科学合理地留存各项任务二的标识符?
你的各项任务一合作开发结束了,发起 pull request / merge request 的这时候在Github/Gitlab 看到 conflict (文件冲突)的提示信息
难题:这是谁的责任?是不是化解?
你被重新分配了各项任务三,但你忘记使用新组成部分,间接在各项任务一的组成部分上写了很多标识符,部分标识符甚至早已执行过 git add 了
难题:是不是恰当地把这些标识符移到一个新组成部分上?
在合作开发各项任务中,你写了很多标识符,有的 git add 过了,有的甚至 git commit 过了,后来发现有很大一部分标识符是重复造轮子,于是决定删掉这些标识符。
难题:是不是使用 git 优雅地撤销指定的标识符 从而 保证在 git log message 中不留下痕迹?看完这些难题,你心里能一点不慌吗?倘若慌或者拿不准,那么恭喜你,接著往上看,你能学到很多。
人类的恐惧通常来自于“未知”和“无法掌控”,认知了Git的结构设计和原理,面对GIt 的难题 你就再也不慌了。
Git 的核心理念结构设计
你们留意过下图中最左列的那些仿佛乱码的字符串吗?
(下图是我的演示项目下 git log –oneline 的输出结果)
倘若执行 git log ,还能看到“乱码”的完整体
这其实是个长度为40的Hash Value(哈希值),Hash Value 能作为识别不同内容的签名 ——也就是内容不同,hash value 一定不同(打 99.999999999% 的包票)
(ps. 对 hash value 完全不认知的同学,能自行去搜索相关科学知识,但这篇该文中不要求深入认知 hash 算法)
Git log 里的乱码其实是 Git 内部的 commit object 的 hash value —— Git 的内部使用了 4 种 object :blob, tree,commit, tag ,我们只要熟悉 commit object 的基本概念就行了,有兴趣的同学再去研究其他几个。
在我们每次执行
的这时候,Git 的内部就生成了一个 commit object
Git 的第一个核心理念结构设计 就是这一个个的 commit object !
在 Git 的内部,只要有 commit object ,你的标识符/文件 就有迹可循,Git 的几乎大部份高级概念都是围绕着 commit object定义的 (比如说 branch,HEAD, tag)
每次生成一个 commit object,你的文件就相当于生成了一个快照 (snapshot),无论你的文件之后如何变化,只要通过git 命令切回这个快照,一切就会恢复到此刻的模样。
此外,每次新生成的 commit object 都会指向前一个 commit object,仿佛一根树枝往上生长。( 不同于 官方文件格式的画法,责任编辑的大部份图片中 commit object 之间的箭头是时间顺序 —— 早commit 的指向 晚commit 的 )
那么什么是 branch(组成部分) 呢?顾名思义,branch 就是让树枝长出分叉来,如下图所示
而合并组成部分(merge)实际上就是 新长出一个 commit object 并且让两个组成部分都指向它(当然文件内容会相应地融合)如下图所示
Git 是不是知道我当前在哪个组成部分?
Git 内部维护着 一个 HEAD ,它类似一个指针,永远指向当前所处的组成部分,比如说下图说明当前在 master 组成部分
第二个核心理念结构设计 ——三个区:Working Tree(工作区),Stage / Index(暂存区) , Local Repo (邻近地区仓库区)
不同的该文用词略有差别,但意思是一样的。
以下是我们跟这三个区打交道的最常见的流程:
当我们对文件做修改时(增删改),文件的改动发生在 工作区 当我们执行 git add 时,对应的文件改动就会被送到 暂存区 当我们执行 git commit 时,对应的文件改动才会进入邻近地区仓库,并且使当前的组成部分向前长了一个 commit object这三个区的特点:
工作区 —— 就是电脑文件系统上的项目文件所处的环境,这是最接近用户的区域,大部份 新增的改动 都是先从工作区开始
暂存区 —— 顾名思义这是个暂时存放文件改动的区域,进退自如 —— 进可进入邻近地区仓库,退可返回到工作区
邻近地区仓库 —— 能进到邻近地区仓库的文件改动都早已有对应的 commit object,因此有迹可循,是三个区中最安全的区域(不容易丢失数据)。因此我推荐尽量把有价值的文件改动 commit 进来,避免无意中丢失,再采用其他方法整理好标识符push上远端仓库。
Git 的大多数命令都跟这三个区在打交道,比如说 add, commit, checkout , reset 甚至是 rebase, cherry-pick,pull 等高级命令, 认知清楚了这三个区和它们的功能,在查阅这些 git 命令的文件格式时也就不至于云里雾里了。
刚刚在说的都是在邻近地区仓库,那远端仓库又是是不是回事?
这就要从 Git 的分布式优点说起了 …… 算了,我们就不多废话,
很单纯,就几点:
1. 使用Git 的项目的备份无处不在 —— 你的邻近地区仓库就是个 备份,你同僚电脑上也有一个备份,远端上(Github, Gitlab …) 也有一份备份
2. 为了管理方便,我们通常统一在 远端仓库上(Github, Gitlab) 做组成部分整合和标识符储存,比如说
(1)能 code review;
(2)方便CI/CD —— 统一从远端上拉取源标识符,编译,打包最后部署
(3)权限控制,比如说大部份人都不准间接 git push origin master ,只有 admin 能 merge develop 或 master 组成部分;
3. 通过远端仓库,项目协作人员方便共享标识符,分工合作。
跟远端相关的Git 命令,我认为最基础的就是 fetch 和 push。
fetch 负责把远端组成部分“拿来”到邻近地区仓库, push负责把邻近地区组成部分推送到远端仓库
而我们常用的 pull 其实同时结合了 fetch 和 checkout (还有 merge 或者 rebase ),详尽的请查阅 git 的文件格式。
最后附上一个特别好的图,供大家学习思考(来自:https://en.wikipedia.org/wiki/Git )
该文开头难题的解答
(好几个听众在评论区里问开头难题的标准答案。我当时不写标准答案的想法是,倘若大家认知了Git的这些基本概念,理论上就能自己推断出标准答案了,请大家多思考,不要轻易做伸手党。看上面标准答案之前,还是希望大家先争取自己思考出科学合理的标准答案)
1. 你被重新分配到了2个分立的各项任务,在完成各项任务一的过程中由于这类因素中止合作开发了(比如说合作开发环境的某一伺服器有难题,先天就可以修通),你不得已去做各项任务二。
难题:那么各项任务四十里写到三分之一的标识符是不是留存?难道这2个各项任务是互相分立的,各项任务二的标识符能在各项任务一的标识符基础上竭尽全力往上写吗?
推荐标准答案
(1). 任务1组成部分feat-1标识符递交到邻近地区仓库 git add . 和 git commit (不推荐使用 git stash,有一定风险丢失标识符——想象一下你在多个这时候不经意地使用了git stash,你还能记得它们的顺序吗?)
(2). 切换到主合作开发组成部分,在主组成部分上重新创建一个新组成部分feat-2合作开发各项任务2
2. 你正在合作开发各项任务二的这时候,同僚化解了一个邻近地区合作开发的操控性难题,且他的标识符早已被 merge 进了主组成部分(倘若你是 后端玩者,能假定 同僚大幅强化了 npm run start 和 webpack hot-deploy 的速率)。
难题:倘若你能用上同僚强化过的这段标识符,你的邻近地区合作开发会大幅提升工作效率。这时你该是不是办?间接 pull 吗?
推荐标准答案
(1). 先 git add . 和 git commit 当前标识符
(2). 再使用git pull –rebase origin develop 以rebase 的形式把远程标识符合进当前组成部分,为什么用 reabse保持git commit 历史记录的整洁,避免引入一次额外的merge的commit3. 你在合作开发各项任务二的这时候,早已写了非常多的标识符,但突然被通告终止各项任务二(倘若这是个 新优点,产品副经理决定中长期时间不来了,但今后可能将还会竭尽全力合作开发 )
难题:是不是科学合理地留存各项任务二的标识符?
推荐标准答案
(1). 先 git add . 和 git commit 当前标识符 (留下清晰的 commit message 和 组成部分名字)
(2). 正常递交到远程新组成部分上去,不合并到主组成部分就是了。等今后有需要,再切换到这个组成部分,竭尽全力合作开发
4. 你的各项任务一合作开发结束了,发起 pull request / merge request 的这时候在Github/Gitlab 看到 conflict (文件冲突)的提示信息
难题:这是谁的责任?是不是化解?
推荐标准答案
原则上是,谁遇到冲突,谁解决冲突
5. 你被重新分配了各项任务三,但你忘记使用新组成部分,间接在各项任务一的组成部分上写了很多标识符,部分标识符甚至早已执行过 git add 了
难题:是不是恰当地把这些标识符移到一个新组成部分上?
推荐标准答案
(1). 先commit当前组成部分的标识符,git log 当前组成部分的commit记录,找到对应commit的hash值
(2). 切换到你要移动的新组成部分上, git cherry-pick hash值,把对应commit的代码复制到当前组成部分6. 在合作开发各项任务中,你写了很多标识符,有的 git add 过了,有的甚至 git commit 过了,后来发现有很大一部分标识符是重复造轮子,于是决定删掉这些标识符。
难题:是不是使用 git 优雅地撤销指定的标识符 从而 保证在 git log message 中不留下痕迹?
推荐标准答案
(1). 先 git add . 和 git commit 当前标识符
(2). 使用 git log 查看邻近地区的 commit 历史,找到合适的回退点 —— 假定对应的 commit hash 是 hash-abc。
(3). 假定当前组成部分名称是 feat-x,使用 git branch -m feat-backup 重命名当前组成部分(新名字叫 feat-x-backup 即备份的意思)—— 这样我们就能完整地备份之前写过的标识符,以备不时之需当后悔药。
不推荐使用 git reset 特别是 git reset –hard ,非常容易丢失标识符,切记,后悔药吃起来非常麻烦!(4). 使用 git co hash-abc 切到一个 commit hash 上,然后 git co -b feat-x 切出一个跟原本组成部分同名的组成部分。 (为什么能 git checkout 到一个 hash 上? 还记得 git branch 的本质吗?)
(5). 倘若以前 feat-x 上过远端,使用 git push -f origin feat-x 强制覆盖远端标识符(虽然通常认为这是危险操作,但由于我们邻近地区有 feat-x-backup 备份,就不用担心了)