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的。
file
点击新建Repository,如下所示:
file
仓库创建成功之后,会出现如下的提示:
file
这些指令的意思是说:(本地操作)

  • 生成一个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四个区域之间的转换关系如下:
file
上述的工作区域之间的变动都是常见的一些Git命令,我们在此不进行详细的介绍了。接下来我们看下文件在多个工作区域变动的时候都有哪些状态吧。

在一个Git仓库,我们执行git status命令,如下所示:
file

  • 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文件的状态
file

  • 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本地仓库,里边存储着你的所有改动等信息。
file

  • 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,如下所示:
    file
    对文件进行add -> commit 之后,objects文件目录会发生变化,也就是说 Git 在每次对本地版本库进行commit的时候,就会对数据 进行一次保存,这是会生成 commit对象,tree对象以及blob对象。
    Git 存储数据内容的方式是为每份内容生成一个文件,取得该内容与头信息的 SHA-1 校验和,创建以该校验和前两个字符为名称的子目录,并以 (校验和) 剩下 38 个字符为文件命名 (保存至子目录下)。通过 cat-file 命令可以将数据内容取回。该命令是查看 Git 对象的瑞士军刀。
    如下所示:
    file
  • 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了开发分支。
file

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方式更新本地代码

file

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
    file
    这也算是应届生考察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出场了,直接看示意图:
file
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:每一行命令都算数的主要内容,如果未能解决你的问题,请参考以下文章

Git:每一行命令都算数

Git:每一行命令都算数

Git:每一行命令都算数

Git:每一行命令都算数

Git:每一行命令都算数

VSCode自定义代码片段——git命令操作一个完整流程