赞
踩
为什么写这篇Git文章?
在日常的需求开发中,发现部分同学不太熟悉Git命令,往往是通过idea自带的一些工具来执行简单的Git命令,遇到一些突发问题的时候,往往不知所措。
简单来说,就是Git基础知识匮乏,不明白每一行Git命令背后的具体含义。所以,本人对Git基础知识进行简单总结与科普,希望可以帮助到大家。
本文阅读前提:
Git是个开源的分布式版本控制系统,最初的目的是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。Gitlab、Github、Gitee指的是业界流行的代码托管平台,都是基于git进行代码仓库管理。
接下来,我们以Github为例,看看正常情况下我们是怎么玩Git的。
点击新建Repository,如下所示:
仓库创建成功之后,会出现如下的提示:
这些指令的意思是说:(本地操作)
当然了这种Git操作是一个项目工程从0 -> 1的过程,也就是当我们需要新建一个项目工程的时候需要执行的一系列操作。
在实际的工作开发中,我们更多的时候是直接通过git clone命令将已经存在的远程仓库中的项目工程克隆到本地。
为了说明我们日常开发中执行的一系列Git命令的作用是什么,我们需要了解Git的工作区域的概念,几乎每一个常见的Git命令操作都可以通过工作区域来解释。
Git本地有四个工作区域:
Git四个区域之间的转换关系如下:
上述的工作区域之间的变动都是常见的一些Git命令,我们在此不进行详细的介绍了。接下来我们看下文件在多个工作区域变动的时候都有哪些状态吧。
在一个Git仓库,我们执行git status命令,如下所示:
所以我们来看下Git文件的状态:
当我们从远程仓库git clone一个项目或者在本地新建一个Git项目执行git init之后,当前目录下会出现一个隐藏的.git文件,这就是Git本地仓库,里边存储着你的所有改动等信息。
ywq@yangwenqiangdeMacBook-Pro .git % cat HEAD
ref: refs/heads/ywq_news_0702
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
在了解Git工作区域、文件状态以及本地仓库的相关信息之后,相信大家对于日常使用的一些命令都有了更加深刻的理解。接下来,我们一起进行一个常用命令总结:
OK,上述是最简单最初级的Git命令,相信我们每一个同学日常都在大量使用。并且如果是单人开发,自己玩的情况下,貌似这些命令就足够了?
是的,够了。但是,在实际的开发当中,我们往往会面对更加复杂的场景,需要一些更为复杂的命令来处理,我们接着往下看。
在当前分支上执行git merge master可以将master的提交合并merge到当前分支,也就是更新本地分支。我们日常开发中,将本地代码推到远程仓库,建立Merge Request,然后点击Merge按钮其实就是在master分支上merge了开发分支。
git fetch可以将远程分支拉到本地:
git fetch 会将所有远程分支都拉到本地
git fetch origin ywq_user_0630 指定拉取远程origin仓库的ywq_user_0630分支到本地
当我们想要更新本地分支代码的时候,需要将远程开发分支或者远程master分支代码拉到本地,并且合并到当前开发分支上。所以git pull = git fetch + git merge
在当前开发分支ywq_news_0702上,我们执行如下的命令:
git pull origin master
表示将远程master分支拉到本地并且merge到当前分支上,也就是使用最新的master代码更新了本地的开发分支ywq_news_0702
正如我们上边git merge所描述的,merge操作会导致提交链路形成一个个的钻石链路,看起来不太清晰,并且会出现很多merge操作所造成的commitId。所以,提供了git rebase命令,这是一种变基操作,一般用在git pull之后,如下所示:
git pull origin master --rebase 使用rebase方式更新本地代码
这个命令和厉害了,可以展现出当前分支上所有的版本commitId,当然是从HEAD开始的。有一句话是这么说的“Git不会丢失任何东西”,你的每一次提交都会被记录下来。通过commitId,我们真的可以操作很多,比如将当前HEAD reset到某个提交上、捡取cherry-pick关键的commit到另一个分支上等等。
这个操作有点意思,他可以将别的分支上的某一个commit捡取到当前分支上。什么意思呢?就是说别人在其他分支上修复了一个bug,你想把修复bug这一段逻辑挪到你的开发分支上,就可以通过cherry-pick来搞定。
比如说我们在本地ywq_news_0702分支上提交了一个commit,如下所示:
这个时候,我们切换到了ywq_vb_0616上,执行:
git cherry-pick e365a0d268d0
注意,对于Git的commitId,我们只要复制的大于等于8位就可以
可以看到效果如下:
特别提醒:
cherry-pick是一个本地操作,也就是说你要捡取的commitId一定是存在于本地仓库其余分支的。你不能直接找一个远程分支存在,但是尚未fetch到本地仓库的commit来执行捡取操作。
这又是一个牛逼的命令,可以重置当前分支的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的核心命令之一,干啥的呢?前面我们说了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真实煞费苦心!有了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中偏实战的内容吧,希望可以对大家有所帮助。
这个是最常见的问题了,当我们进行merge、pull、rebase、cherry-pick操作的时候,都可能会产生冲突。代码冲突是啥意思?
冲突是指多个开发者对于同一个位置都做出了修改,导致合并操作的时候无法自动合并的行为。
遇到冲突的时候不要慌,见过一些同学,遇到冲突特别慌,当看到大量冲突的时候,不想解决了,竟然选择了 手动备份本地代码 + 删除本地仓库,重新clone远程仓库的操作 (哭泣.jpg)
遇到冲突的时候,各个命令都会给出明确的提示:
冲突,无非就是我们的工作区有了一些代码,如果你了解Git的工作区域和工作流程,废弃掉这些所谓的冲突代码,结束本次操作就是轻而易举的事情。
怎样尽量避免冲突?
多人开发的时候,在一些枚举等场景下会经常出现冲突的情况,有一个小技巧是,在不要求顺序的一些场景下,我们不要在最后的位置继续添加Enum或者ErrorCode等代码,否则极易造成代码冲突(当然了,解决冲突也是很easy的,但是次数多了也很烦)。
有这样一个需求,代码量不多,但是因为你多次提交,会导致在建立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会感觉倍儿有面儿。
当我们在当前分支开发某个需求的时候,遇到了另一个需求的联调问题,需要切换到另一个分支上去解决问题。怎么办?
正常情况下,我们应该将当前分支工作区的内容add 、commit之后再切换分支。但是问题来了,当前需求开发了一半,我不想生成一次提交怎么办?
放心,这个时候我们的git stash命令可以帮助我们将当前工作区的内容储藏起来。然后切换其他分支,处理完问题之后,再切换到当前分支,执行git stash pop取出来就完事。
git stash list // 查看当前stash里边的内容
git stash // 将当前工作区内容储藏起来
git stash pop // 将stash中栈顶内容pop出来,当然也可以根据顺序直接取第n个
背景是这样的:有一个远程开发分支news_slide,多个开发者都在本地的news_slide分支上开发相关逻辑,并且push到远程开发分支上。
小明辛辛苦苦的改动了几十个文件,码了上千行代码。在执行git push origin news_slide的时候,提示,远程代码更加新(因为其余人在此期间将相关代码提交到了远程分支上了),需要执行git pull更新本地开发分支的代码。
小明直接执行了如下命令:
git pull origin master --rebase
执行完毕之后,才发现不对,本来准备拉取的是远程开发分支news_slide的最新代码,结果这次将master代码rebase下来了。这会导致将本地分支推送的时候,提示需要更新当前分支版本,导致必须执行-f操作才可以强行提交,但是这样会将远程别小伙伴提交的代码丢掉。
怎么办?难道只能通过本地备份者一千多行代码了吗?(奔溃.jpg)
当然不是,解决办法如下:
主要指出的是:当只有你自己开发的时候,更新代码可以使用git pull origin master --rebase,push的时候可以直接-f就完事~
一个项目需要多人合作开发的时候,我们先给出一个案例。
方式一:
远程master分支、本地master分支
执行
其余开发同学在自己的工具上,执行
在开发的过程中,将commit从本地开发分支到本地news_slide分支,然后push到远程(当然也可以选择远程建立Merge Request的方式)
将news_slide分支代码更新到本地的开发分支上继续开发。当需要更新master分支上的最新内容时候,前面我们说了不可以使用rebase方式,所以我们可以使用merge master(前提是先切换到master分支更新代码)方式搞定。
特别注意:这里我们再次说下为什么不可以使用rebase方式更新master内容?
因为rebase是一种变基操作,新生成的commitId会被认为落后于远程的HEAD,所以必须使用-f来提交,导致丢失其余同学提交的内容的问题出现。
方式二:
其实,我一直觉得方式一看起来有点啰嗦,简洁一点就是说,多个开发者都直接在本地的news_slide分支上开发,不需要创建个人开发分支,只需要在每一次push之前,先将远程开发分支的内容pull下来即可(也就是更新分支的HEAD)
当我们执行git add之后,文件会被添加到staged中,这个时候,我们执行git diff会发现没有任何diff出来。怎么办?我就想看现在在staged中的内容是啥?(因为我感觉我add错了一些东西)难道我只能push到远程,建立Merge Request来查看刚刚add了啥吗?
No,我们先来看下git diff是在干嘛?
实际操作效果如下:
Git相关知识点太多了,结合自身实际使用,本文给出了一些阐述和介绍。希望大家可以熟悉Git的工作流程以及相关的文件流转状态。在此基础上,我们才可以准备理解每一个命令背后的实际行为,从而可以提高工作效率,减少各种误操作,及时喝下“后悔药水”。
“Git就像一条狗,它能闻出你的恐惧”。Git是一个很好的分布式版本控制系统,你的每一次操作都会被记录,如果我们能够熟练使用常见命令就可以轻松玩转Git。
“Git,每一行命令都算数”。在日常的开发中,大量的同学习惯使用图形化界面来操作Git,这样会导致大家对Git命令不甚熟悉,如果出现异常情况会懵逼。对于常年手敲Git命令的我来说,“每一行命令都算数,我只相信我敲出来的命令”。
限于本人水平,文中难免会有不妥甚至错误之处,烦请各位指出。
2021.07.03 夜
谨以此万字长文,献给结束大小周后的第一个双休(开心.jpg)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。