Git:每一行命令都算数
Posted 温柔狠角色
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Git:每一行命令都算数相关的知识,希望对你有一定的参考价值。
前言
为什么写这篇Git文章?
在日常的需求开发中,发现部分同学不太熟悉Git命令,往往是通过idea自带的一些工具来执行简单的Git命令,遇到一些突发问题的时候,往往不知所措。
简单来说,就是Git基础知识匮乏,不明白每一行Git命令背后的具体含义。所以,本人对Git基础知识进行简单总结与科普,希望可以帮助到大家。
本文阅读前提:
- 在日常工作中,会使用简单的Git命令
- 了解简单命令,不熟悉复杂一点的命令操作
- 大概了解Git的基础知识,对命令实际行为不甚了解
正文
Git是什么?
Git是个开源的分布式版本控制系统,最初的目的是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。Gitlab、Github、Gitee指的是业界流行的代码托管平台,都是基于git进行代码仓库管理。
Git怎么玩?
接下来,我们以Github为例,看看正常情况下我们是怎么玩Git的。
点击新建Repository,如下所示:
仓库创建成功之后,会出现如下的提示:
这些指令的意思是说:(本地操作)
- 生成一个README.md文件
- 将当前目录初始化为Git仓库
- 将文件add到暂存区(index)
- 将本次修改提交到本地仓库
- 将当前分支master重命名为main分支
- 添加远程origin仓库
- 将本地Git仓库中的内容推送到远程origin仓库的main分支上
当然了这种Git操作是一个项目工程从0 -> 1的过程,也就是当我们需要新建一个项目工程的时候需要执行的一系列操作。
在实际的工作开发中,我们更多的时候是直接通过git clone命令将已经存在的远程仓库中的项目工程克隆到本地。
Git工作区域
为了说明我们日常开发中执行的一系列Git命令的作用是什么,我们需要了解Git的工作区域的概念,几乎每一个常见的Git命令操作都可以通过工作区域来解释。
Git本地有四个工作区域:
- 工作区(Working Directory):在git init之后的本地的文件目录下,也就是大家写代码的地方
- 暂存区(Staged/Index):修改了代码之后,需要先将改动add到暂存区,表示将要提交的改动
- 本地仓库(Local Repository):本地Git仓库,通俗讲就是本地隐藏文件.git目录下,存储着你的所有改动
- 远程仓库(Remote Repository):远程Git仓库,理论上和本地仓库地位平等,但是主要是用于多个开发者之间pull/push代码的仓库。
Git四个区域之间的转换关系如下:
上述的工作区域之间的变动都是常见的一些Git命令,我们在此不进行详细的介绍了。接下来我们看下文件在多个工作区域变动的时候都有哪些状态吧。
在一个Git仓库,我们执行git status命令,如下所示:
- On branch ywq_news_0702告诉我们当前在哪个分支上
- Changes not staged for commit:告诉我们目前有哪些文件时Changed但是没有被放到暂存区staged的,并且下边两行括号中时告诉我们可以采取的操作:
- git add可以将改动添加到staged中for commit
- git restore可以将在工作区的改动丢弃掉
- Untracked files:未跟踪的文件,表示这是一个新文件,还没有被加入到Git仓库中,也就不参与版本控制:
- 通过git add命令可以将其包含在staged中for commit
- 通过git add命令可以将其包含在staged中for commit
所以我们来看下Git文件的状态:
- UnTracked: 未跟踪,此文件在文件夹中,但并没有加入到git库,不参与版本控制。通过git add 状态变为Staged。
- UnModify: 文件已经入库,未修改, 即版本库中的文件快照内容与文件夹中完全一致。这种类型的文件有两种去处,如果它被修改,而变为Modified。如果使用git rm移出版本库,则成为UnTracked文件。
- Modified: 文件已修改,仅仅是修改,并没有进行其他的操作。这个文件也有两个去处,通过git add可进入暂存staged状态,使用git checkout 则丢弃修改过,返回到unmodify状态,这个git checkout即从库中取出文件,覆盖当前修改。
- Staged: 暂存状态,执行git commit则将修改同步到库中,这时库中的文件和本地文件又变为一致,文件为UnModify状态。执行git reset 取消暂存,文件状态为Modified。
Git本地仓库
当我们从远程仓库git clone一个项目或者在本地新建一个Git项目执行git init之后,当前目录下会出现一个隐藏的.git文件,这就是Git本地仓库,里边存储着你的所有改动等信息。
- HEAD:存储的当前分支的HEAD信息
ywq@yangwenqiangdeMacBook-Pro .git % cat HEAD
ref: refs/heads/ywq_news_0702
- refs:存储着各个分支最新的commitId
ywq@yangwenqiangdeMacBook-Pro .git % cat HEAD
ref: refs/heads/ywq_news_0702
ywq@yangwenqiangdeMacBook-Pro .git % cat refs/heads/ywq_news_0702
e821909206034c15a7f087885db5d47ef0462dd2
e821909206034c15a7f087885db5d47ef0462dd2这个就是本地ywq_news_0702分支上的最新提交HEAD
- config:存储着Git仓库的配置文件
- objects:Git本地仓库存储的所有对象,Tree、Commit、Blob类型
可以看到我们每一次提交都是产生了一个40位的commitId,objects里边存储了一个个的Tree,会以commitId的前两位作为tree,如下所示:
对文件进行add -> commit 之后,objects文件目录会发生变化,也就是说 Git 在每次对本地版本库进行commit的时候,就会对数据 进行一次保存,这是会生成 commit对象,tree对象以及blob对象。
Git 存储数据内容的方式是为每份内容生成一个文件,取得该内容与头信息的 SHA-1 校验和,创建以该校验和前两个字符为名称的子目录,并以 (校验和) 剩下 38 个字符为文件命名 (保存至子目录下)。通过 cat-file 命令可以将数据内容取回。该命令是查看 Git 对象的瑞士军刀。
如下所示:
- index:存储着Git索引文件
- logs:存储着每一次的commit操作:
Git命令解析
在了解Git工作区域、文件状态以及本地仓库的相关信息之后,相信大家对于日常使用的一些命令都有了更加深刻的理解。接下来,我们一起进行一个常用命令总结:
- git clone:将远程仓库克隆到本地,也就是创建了一个本地仓库,会出现隐藏文件.git
- git status:查看状态,可以看到哪些文件被修改、哪些是未跟踪的文件
- git diff:可以看到当前工作区和暂存区staged中的文件diff
- git add:将未跟踪(新增加)或者修改过的文件从工作区添加到暂存区staged中
- git commit:将暂存区staged中的内容提交到本地Git仓库中
- git push:将本地Git仓库中的内容提交到远程Git仓库中
OK,上述是最简单最初级的Git命令,相信我们每一个同学日常都在大量使用。并且如果是单人开发,自己玩的情况下,貌似这些命令就足够了?
是的,够了。但是,在实际的开发当中,我们往往会面对更加复杂的场景,需要一些更为复杂的命令来处理,我们接着往下看。
git merge
在当前分支上执行git merge master可以将master的提交合并merge到当前分支,也就是更新本地分支。我们日常开发中,将本地代码推到远程仓库,建立Merge Request,然后点击Merge按钮其实就是在master分支上merge了开发分支。
git fetch
git fetch可以将远程分支拉到本地:
git fetch 会将所有远程分支都拉到本地
git fetch origin ywq_user_0630 指定拉取远程origin仓库的ywq_user_0630分支到本地
git pull
当我们想要更新本地分支代码的时候,需要将远程开发分支或者远程master分支代码拉到本地,并且合并到当前开发分支上。所以git pull = git fetch + git merge
在当前开发分支ywq_news_0702上,我们执行如下的命令:
git pull origin master
表示将远程master分支拉到本地并且merge到当前分支上,也就是使用最新的master代码更新了本地的开发分支ywq_news_0702
git rebase
正如我们上边git merge所描述的,merge操作会导致提交链路形成一个个的钻石链路,看起来不太清晰,并且会出现很多merge操作所造成的commitId。所以,提供了git rebase命令,这是一种变基操作,一般用在git pull之后,如下所示:
git pull origin master --rebase 使用rebase方式更新本地代码
git log
这个命令和厉害了,可以展现出当前分支上所有的版本commitId,当然是从HEAD开始的。有一句话是这么说的“Git不会丢失任何东西”,你的每一次提交都会被记录下来。通过commitId,我们真的可以操作很多,比如将当前HEAD reset到某个提交上、捡取cherry-pick关键的commit到另一个分支上等等。
git cherry-pick
这个操作有点意思,他可以将别的分支上的某一个commit捡取到当前分支上。什么意思呢?就是说别人在其他分支上修复了一个bug,你想把修复bug这一段逻辑挪到你的开发分支上,就可以通过cherry-pick来搞定。
比如说我们在本地ywq_news_0702分支上提交了一个commit,如下所示:
这个时候,我们切换到了ywq_vb_0616上,执行:
git cherry-pick e365a0d268d0
注意,对于Git的commitId,我们只要复制的大于等于8位就可以
可以看到效果如下:
特别提醒:
cherry-pick是一个本地操作,也就是说你要捡取的commitId一定是存在于本地仓库其余分支的。你不能直接找一个远程分支存在,但是尚未fetch到本地仓库的commit来执行捡取操作。
git reset
这又是一个牛逼的命令,可以重置当前分支的HEAD指针,常见的参数如下所示:
-
git reset commitId
-
git reset --soft commitId
-
git reset --hard commitId
这也算是应届生考察Git常见的八股文系列了吧,但是仍然会有好多人不明白其中具体的区别和用途,这里结合Git工作区域来做一个解释: -
git reset commitId
该命令执行之后,HEAD指针会移动到选中commitId上,并且之前的HEAD ->commitId之间的所有修改的内容会被置于工作区,需要重新add、commit。如下所示:
-
git reset --soft commitId
该命令执行之后,HEAD指针会移动到选中commitId上,并且之前的HEAD ->commitId之间的所有修改的内容会被直接置于暂存区staged中,也就是后续只需要执行commit操作就OK了。如下所示:
-
git reset --hard commitId
这个–hard很好理解,就是在回滚HEAD指针的时候,很强硬的将所有HEAD ->commitId之间的改动内容“全部删除”!
为什么加引号?因为前边我们说了Git不会丢失任何你提交过的内容(只要你玩的溜),后续我们会分析原因。
git reflog
这个命令那简直就是Git的核心命令之一,干啥的呢?前面我们说了git log可以看到当前分支上的所有commit,通过commitId我们可以进行一些reset、cherry-pick操作。reflog里存储了当前HEAD(包含各个分支)指针曾经指向过的每一个commitId,只要有了commitId,我们就可以回滚操作,也就拥有了“后悔药”。
细心的同学会发现我上边演示git reset操作的时候,反反复复都在操作一个commtId:4e005bec1617。明明已经reset了,我为什么还能反复操作呢?
答:因为我执行了git reflog找到了刚刚的HEAD,并且执行git reset --hard 4e005bec1617将当前分支的HEAD重置回去了。
因为当我们reset HEAD指针之后,git log里只会显示当前分支HEAD之前的所有commitId,所以我们需要借助于git reflog来完成操作。
git revert
为了出售“后悔药”,Git真实煞费苦心!有了git log、git reflog以及git reset还不行。Git提供了revert命令来进行后悔操作。什么场景使用git revert命令呢?
当我们向master merge了一些代码,过了几个小时才发现有bug,整体逻辑都不太对。怎么办?
有同学说,我找到commitId,直接reset --hard就完事呗?No,那可不行,在这段时间内,在你的commitId之后,其余同学都提交了一系列的commit了,你肯定不能影响到别人的提交。
这个时候git revert出场了,直接看示意图:
revert命令实现了HEAD指针的继续前进,新生成的commit和要撤销的目标提交具有相反的操作,实现了“后悔”的操作。
Git常见的一些小技巧
其实,写这篇文章的初衷是讲述一些干货。但是写着写着才发现Git这玩意儿比较零碎,很难写出真正的干货,一不小心就成了一片“万字水文(科普文)”了。接下来,我们讲解一些Git中偏实战的内容吧,希望可以对大家有所帮助。
解决冲突:
这个是最常见的问题了,当我们进行merge、pull、rebase、cherry-pick操作的时候,都可能会产生冲突。代码冲突是啥意思?
冲突是指多个开发者对于同一个位置都做出了修改,导致合并操作的时候无法自动合并的行为。
遇到冲突的时候不要慌,见过一些同学,遇到冲突特别慌,当看到大量冲突的时候,不想解决了,竟然选择了 手动备份本地代码 + 删除本地仓库,重新clone远程仓库的操作 (哭泣.jpg)
遇到冲突的时候,各个命令都会给出明确的提示:
- git merge操作会告诉你哪些文件是both modified:
- 需要你解决冲突之后,执行git add、commit
- 或者直接执行git merge --abort废弃掉本次merge操作
- 或者执行git reset --hard HEAD来将本地工作区、暂存区内容强行恢复到本地的HEAD上(这条是我加的)
- git rebase操作会告诉你哪些文件是both modified:
- 需要你解决冲突之后,执行git add、git rebase --continue命令继续执行rebase操作;
- 或者直接执行git rebase --abort废弃掉本次rebase操作
冲突,无非就是我们的工作区有了一些代码,如果你了解Git的工作区域和工作流程,废弃掉这些所谓的冲突代码,结束本次操作就是轻而易举的事情。
怎样尽量避免冲突?
多人开发的时候,在一些枚举等场景下会经常出现冲突的情况,有一个小技巧是,在不要求顺序的一些场景下,我们不要在最后的位置继续添加Enum或者ErrorCode等代码,否则极易造成代码冲突(当然了,解决冲突也是很easy的,但是次数多了也很烦)。
手动整理commit
有这样一个需求,代码量不多,但是因为你多次提交,会导致在建立Merge Request的时候,出现了几十个commitId,比如:“update”、“bug fix”、“fix again”等,不光看起来很丑,也会给大家一种感觉,这小伙到底行不行呀?一百行代码的开发量,提交了这么多次才搞定。(尴尬.jpg)
为了解决这样的问题,我们可以巧妙的利用git reset。比如当前的commit是这样的A-1-2-3-4-5-6-7-8,你的第一个提交是1,那么我们执行如下的命令:
git reset --soft A // 重置本地分支HEAD指针
git commit -m "XXX逻辑开发"
git push origin ywq_news_0702 -f // 提交到远程分支
**-f是什么操作?force的含义,表示强行执行本次操作。**当我们git reset之后,本地的HEAD指针指向的commitId会比远程origin对应的落后,直接push会被拒绝。通过-f命令可以强行将本地内容push到远程分支上(切记!如果是多人共同合作的开发分支或者远程master操作,千万不能加-f操作!!!)
经过git reset --soft之后,我们提的Merge Request里就是一个commitId了,发出来的CR会感觉倍儿有面儿。
git stash临时储藏
当我们在当前分支开发某个需求的时候,遇到了另一个需求的联调问题,需要切换到另一个分支上去解决问题。怎么办?
正常情况下,我们应该将当前分支工作区的内容add 、commit之后再切换分支。但是问题来了,当前需求开发了一半,我不想生成一次提交怎么办?
放心,这个时候我们的git stash命令可以帮助我们将当前工作区的内容储藏起来。然后切换其他分支,处理完问题之后,再切换到当前分支,执行git stash pop取出来就完事。
git stash list // 查看当前stash里边的内容
git stash // 将当前工作区内容储藏起来
git stash pop // 将stash中栈顶内容pop出来,当然也可以根据顺序直接取第n个
多人开发中的git pull误操作
背景是这样的:有一个远程开发分支news_slide,多个开发者都在本地的news_slide分支上开发相关逻辑,并且push到远程开发分支上。
小明辛辛苦苦的改动了几十个文件,码了上千行代码。在执行git push origin news_slide的时候,提示,远程代码更加新(因为其余人在此期间将相关代码提交到了远程分支上了),需要执行git pull更新本地开发分支的代码。
小明直接执行了如下命令:
git pull origin master --rebase
执行完毕之后,才发现不对,本来准备拉取的是远程开发分支news_slide的最新代码,结果这次将master代码rebase下来了。这会导致将本地分支推送的时候,提示需要更新当前分支版本,导致必须执行-f操作才可以强行提交,但是这样会将远程别小伙伴提交的代码丢掉。
怎么办?难道只能通过本地备份者一千多行代码了吗?(奔溃.jpg)
当然不是,解决办法如下:
- 通过git reflog,找到当前HEAD指针上一次指向的commitId,也就是找到你rebase master之前的HEAD commitA。
- 执行git reset --hard commitA,则代码可以恢复到小明误操作之前。
主要指出的是:当只有你自己开发的时候,更新代码可以使用git pull origin master --rebase,push的时候可以直接-f就完事~
多人合作开发模式
一个项目需要多人合作开发的时候,我们先给出一个案例。
方式一:
远程master分支、本地master分支
执行
- git pull origin master --rebase 更新本地master分支。
- git checkout -b news_slide 创建开发分支
- git push origin news_slide 将当前news_slide推送到远程仓库,作为远程公共开发分支
- git checkout -b news_slide_1 创建本地个人开发分支1
其余开发同学在自己的工具上,执行
- git fetch origin news_slide 将远程news_slide分支拉到本地
- git checkout news_slide 、git checkout -b news_slide_2 创建本地开发分支2
在开发的过程中,将commit从本地开发分支到本地news_slide分支,然后push到远程(当然也可以选择远程建立Merge Request的方式)
将news_slide分支代码更新到本地的开发分支上继续开发。当需要更新master分支上的最新内容时候,前面我们说了不可以使用rebase方式,所以我们可以使用merge master(前提是先切换到master分支更新代码)方式搞定。
特别注意:这里我们再次说下为什么不可以使用rebase方式更新master内容?
因为rebase是一种变基操作,新生成的commitId会被认为落后于远程的HEAD,所以必须使用-f来提交,导致丢失其余同学提交的内容的问题出现。
方式二:
其实,我一直觉得方式一看起来有点啰嗦,简洁一点就是说,多个开发者都直接在本地的news_slide分支上开发,不需要创建个人开发分支,只需要在每一次push之前,先将远程开发分支的内容pull下来即可(也就是更新分支的HEAD)
如何查看暂缓区staged中的内容?
当我们执行git add之后,文件会被添加到staged中,这个时候,我们执行git diff会发现没有任何diff出来。怎么办?我就想看现在在staged中的内容是啥?(因为我感觉我add错了一些东西)难道我只能push到远程,建立Merge Request来查看刚刚add了啥吗?
No,我们先来看下git diff是在干嘛?
- git diff 比较的是当前工作区和最后一次提交到本地仓库(HEAD)的内容的差异
- git diff --cache 比较的是当前暂存区和最后一次提交到本地仓库(HEAD)的内容的差异
- git diff --staged 同–cached
实际操作效果如下:
总结:
Git相关知识点太多了,结合自身实际使用,本文给出了一些阐述和介绍。希望大家可以熟悉Git的工作流程以及相关的文件流转状态。在此基础上,我们才可以准备理解每一个命令背后的实际行为,从而可以提高工作效率,减少各种误操作,及时喝下“后悔药水”。
结束语:
“Git就像一条狗,它能闻出你的恐惧”。Git是一个很好的分布式版本控制系统,你的每一次操作都会被记录,如果我们能够熟练使用常见命令就可以轻松玩转Git。
“Git,每一行命令都算数”。在日常的开发中,大量的同学习惯使用图形化界面来操作Git,这样会导致大家对Git命令不甚熟悉,如果出现异常情况会懵逼。对于常年手敲Git命令的我来说,“每一行命令都算数,我只相信我敲出来的命令”。
限于本人水平,文中难免会有不妥甚至错误之处,烦请各位指出。
2021.07.03 夜
谨以此万字长文,献给结束大小周后的第一个双休(开心.jpg)
以上是关于Git:每一行命令都算数的主要内容,如果未能解决你的问题,请参考以下文章