Git学习入门
前言
本文为这段时间的Git学习总结,从最基础的定义到我们日常常用的命令、Git不同分支管理以及底层的原理都有详细的介绍,本文主要参考git-scm.com/book/zh/v2 大家想深入学习可以去阅读该书
VCS介绍 (版本控制系统)
版本发展历史
CVCS(Centralized Version Control Systems)
代表:SVN
特点:中心化的服务器保存着文件的所有版本,其他服务器从中央服务器获取最新的版本文件,但是如果中央服务器发生故障,那么这段时间所有人都无法提交更新或者拉取最新的版本。由于中心化的保存文件会出现如果中央服务器磁盘发生故障,并且在没有备份的情况下会丢失掉所有的文件以及历史版本,每个人的机器上只是保存了当时拉取到的最新的文件,即使使用这种方式进行数据复原也无法恢复历史版本。
文件版本存储方式:
保留源文件,对于版本变更保存每次的变更信息。可以将这种存储看成是一组基本文件和每个文件随着时间逐步积累的差异
DVCS(Distributed Version Control System)
代表:Git
特点:每个服务器都会拷贝所有文件以及所有的版本记录。并且在网络或者服务器不可用的时候可以在本地进行提交,待网络恢复之后再进行同步。相对于CVCS由于每个服务器都拷贝了仓库所有的信息,当中央仓库发生故障导致资料信息丢失时可以使用其他的服务器上进行数据恢复,虽然也可能丢失部分版本,但是大量历史版本还是能被保存。
文件版本存储方式:
Git每次提交都会对全部文件创建一个快照,并且保存这个快照的索引(为了提高效率如果文件没有发生修改的情况下只会新建一个文件指向源文件的链接)
Git的三棵树
在我们git操作中一般都是在3个区域中进行文件的处理,分别是工作区、暂存区(索引)、HEAD指针
本地仓库由 git 维护的三棵“树”组成。 第一个是工作目录,实际就是我们计算机中的文件夹; 第二个是暂存区(Index) ,它像个缓存区域,临时保存改动; 最后是 HEAD,它指向你最后一次提交的结果
简单理解:
在我们本地直接操作,比如说新增、删除、修改文件,这些都是我们的工作区。当我们执行执行git checkout这样的命令时,git会对我们的工作区进行修改,将工作区文件变成所切换分支的文件快照。
当我们执行git add 将文件交给git管理的时候,文件就被放入到暂存区,在git中学名是索引。
当我们执行git commit时,git会创建新的提交对象,并将我们的HEAD指针前移指向最新的提交对象。
Git分支原理
Git 保存的不是文件的变化或者差异,而是一系列不同时刻的快照,在进行提交操作时,Git 会保存一个提交对象(commit object)
举个例子:
我们当前有个有一个工作目录,里面包含了三个将要被暂存和提交的文件。暂存操作会为每一个文件计算hash,然后会把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交。Git 仓库中有五个对象:三个blob对象(保存着文件快照)、一个树对象(记录着目录结构和 blob 对象索引)以及一个提交对象包含着指向前述树对象的指针和所有提交信息)
后续我们的提交都会创建一个新的对象,并且有一个指向上一次提交对象的指针。
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。Git 的默认分支名字是 master。在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master 分支。master 分支会在每次提交时自动向前移动
tip:master分支并不是一个特殊的分支,只是新建的时候默认叫这个,github现在的默认分支已经是main了
在上图中还有一个head分支,这个也是一个指针,它的指向就是我们当前所处的分支
当我们执行checkout命令切换分支时,其实就是将head指针的指向进行了转移
我们的每次对分支进行提交的时候都会新建一个提交对象并且我们当前的分支代表的指针会进行向前移动。因为我们现在所在的分支是testing分支,HEAD指针就是指向testing,当我们执行命令切换到master分支时,HEAD指针就会指向master,同时会将我们的工作目录恢复到master分支指向的快照内容
git branch
git branch在不带任何参数的情况下会展示所有的分支,一般来说我们可以使用这个命令来创建和删除新的分支。
git branch xxx 创建一个新分支
git branch -d xxx 删除分支(如果这个分支与当前所在分支没有进行合并的话无法删除、使用-D可以强制删除)
git checkout
git checkout 通常都是用来切换分支的,对于三棵树的操作是,先将我们的HEAD中选中的分支的文件快照复制到暂存区,再用暂存区的文件覆盖我们的工作区。当我们直接修改我们的工作区的文件,如果在没有进行提交的情况下进行checkout切换分支会把这次的改动带到新切换的分支上去,但是如果修改的文件上有冲突在分支切换的时候就会提示有冲突切换失败,这个时候可以加上-f 强制切换,但是要注意执行这个操作我们的改动就会丢失
使用git log命令可以查看分支的提交记录,我们当前future分支被HEAD指针指向,也就说明HEAD就是标记了当前我们所处的分支,另外还有master分支和history分支,他们落后于我们现在的分支,还指向了前面的提交。
分支的合并
我们当前有一个分支,主分支就是master,这时候有个需求需要进行开发,我们从master分支中切出来一个开发分支iss53
git checkout -b iss53
这时候突然发现线上出了一个bug,我们不得不暂停开发,再从master分支切下一个新的分支进行bug修改,如下图
开发结束后,我们将hotfix合并到master分支之后,master所指向的指针就会移动到原来hotfix指向的提交
当我们正常的开发完成之后需要将我们的开发分支iss53合并到master,但是和之前的hotfix不同,iss53的父提交不再是master,他现在和master有一个共同的父提交,图中C2,
这时候git的操作就是将iss53和master这两个所处的提交C4、C5以及他们共同的父提交C2进行合并,并且生成一个新的提交
但是在我们的合并中并不是顺利的,有可能会存在冲突的情况,这时候Git 做了合并,但是没有自动地创建一个新的合并提交。Git 会暂停下来,等待你去解决合并产生的冲突。
使用git status命令可以看到当前存在冲突的文件。
git stash
在工作中我们经常会碰到这样的问题,我正在一个分支上开发新的功能,突然遇到一个紧急的需求或者bug,需要切换到其他分支去处理,这时候很多人会先将代码进行临时提交,然后切换到其他分支,这样的话我们的代码的提交历史会变得很长,有些改动很难准确的找到哪次提交提上去的,这时候可以使用git stash命令来帮助我们解决这个问题,git stash命令会将我们的暂存区的数据暂时存储起来,之后会清空我们的暂存区,但是不会马上提交,这样我们就可以切换到其他分支进行工作,当我们需要的时候可以重新将我们存储的文件进行恢复,使用git stash apply即可
git rebase
除了merge使用git rebase也可以实现分支上的合并,不过rebase有一个更重要的作用那就是在不同分支中部分改动的转移
在我们的实际开发中,可能存在因为一些特殊的业务需求对于不同的环境使用了一些特殊的逻辑,而因为这些逻辑我们无法将这些代码合并到我们的主分支中,从而不得不维护多个主分支。当开发的业务越多,两个分支的差异会越来越大,当我们某一天需要再两个分支中开发相同的代码中,就会出现一个麻烦,如果需求的改动比较小还好,可以手动在另一个分支上重写一遍代码,但是如果改动特别大,涉及很多的文件,那么手动再次修改就会很费时费力,但是由于特殊的代码逻辑存在又不能进行merge操作,这时候rebase可以帮我们轻松的解决这个问题,下面就是一个示例
配置
git安装之后查看每次git提交的用户名和邮箱
也可以通过下面的命令进行设置
git config --global user.name "wangjinxin"
git config --global user.email wjxScott@gmail.com”
对于一个已经被Git托管的仓库每次对文件进行修改在命令行中需要使用git add将修改的文件进行暂存之后再进行git commit,否则文件的修改就会被丢失。在平时使用idea开发过程中由于设置了自动对文件进行add这一点经常会忽略,需要注意这个小点。
git在提交的时候还提供了一个参数 git commit -a,他会自动将之前已经跟踪的文件自动暂存起来一起提交,免去了我们每次都要add文件的问题(修改文件之后直接git commit -a -m 'xxx'不用先执行git add xxx ,新增加的文件无效[必须之前已经被git管理但是又发生修改,如果这个文件没有被git管理这个命令是无法操作这些文件的])
gitignore的文件匹配:
gitignore使用glob模式进行匹配,所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。星号(*)匹配零个或多个任意字符;[abc] 匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(?)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符, 表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。使用两个星号()表示匹配任意中间目录,比如 a//z 可以匹配 a/z 、 a/b/z 或 a/b/c/z 等
示例:
# 忽略所有的 .a 文件
*.a
# 但跟踪所有的 lib.a,即便你在前面忽略了 .a 文件
!lib.a
# 只忽略当前目录下的 TODO 文件,而不忽略 subdir/TODO
/TODO
# 忽略任何目录下名为 build 的文件夹
build/
# 忽略 doc/notes.txt,但不忽略 doc/server/arch.txt
doc/*.txt
# 忽略 doc/ 目录及其所有子目录下的 .pdf 文件
doc/**/*.pdf
文件操作命令
git rm xxx命令可以将文件从git仓库中移除追踪,同时会删除我们工作区的文件。如果我们不小心将log之类的文件放到git的版本管理中可以使用这个命令对文件进行移除管理,文件只会存在在我们的磁盘中,但是git仓库不会再追踪(git rm --cached)
git clean 是处理未被跟踪的文件(没有执行git add),无法处理已经add文件,相反 git rm处理的是已经被git 管理的文件,对于没有给git管理的无法处理,他们的处理区域分别是
git clean处理工作区,git rm 处理暂存区,注意这两个命令执行之后都会将工作区的文件删除
如果我们对文件进行了修改但是还没有提交的话,那么这个文件的修改可以使用git checkout -- filename来处理,这个命令会使用最近的一个commit来替换文件,如果已经commit则不会生效
git对远程分支的管理
git clone xxxx.git这是将远程分支克隆到本地,这个命令会复制远程仓库中代码和所有版本信息到本地,默认分支就是master
git remote命令可以用来来查看分支的远程信息
这个命令展示远程分支origin,这个是一个简称,我们远程默认,加上-v这个参数可以查看具体的url信息
git push是将本地分支推送到远程,也可以用这个命令来删除远程的分支
git push 远程名 -d 分支名
git reset
git reset命令的操作其实就是对这三个区域的操作。
git reset 后面主要参数是 soft、mixed、hard,其实他们分别影响到git中的三个区域
我们的HEAD指针进行了一次后移(这个指针的移动和checkout不同,理解为指针的指针发生了移动),相当于丢弃了我们之前的一次提交,但是暂存区和工作区没有发生变化。我们看到效果就好像是执行了git add但是没有执行git commit
git reset --mixed HEAD~ 或者 git reset HEAD~
执行上面的命令之后我们的git文件变化如下
git reset --hard HEAD~ 命令执行之后会将3个区域同时回滚
这个操作是很危险的,因为这样会丢弃掉所有的改动,除非之前已经将commit push到远程机器,否则无法找回修改
注意我们刚才的回滚操作都是在我们本地操作的,如果要回滚远程分支我们直接push会被拒绝,因为Git拒绝我们提交一个落后的分支覆盖远程分支,我们只能使用参数-f进行强制推送
标签
git可以对分支进行打标签,表示重要的节点,比如某个重大版本的发布。我们可以简单的理解为是标签是某一次commit的别名
git标签分为两种,分别是注释标签和轻量标签
- 轻量标签:对某次commit的引用
- 注释标签:存储在git仓库中的一个完整对象,包含打标签者的名字、电子邮件地址、日期时间 以及其他的标签信息。可以使用hash进行校验
轻量标签使用git tag 标签名来创建
注释标签使用 git tag -a 标签名 -m 注释内容 来创建
使用git push 远程名 tag名 可以推送本地的tag到远程服务器,这个命令与推送分支完全相同
git push 远程名 --tag 可以推送本地全部的tag到远程服务器
通过标签checkout 分支:
我们可以通过checkout标签名来指向某个指定的tag版本,但是我们这个时候会处于一个 分离头指针的状态
看上图的实例中我们从master分支切换到tag1的标签,这时候我们的分支这边的信息展示的一串hash值,这是tag对应的commit的hash。
在“分离头指针”状态下,如果你做了某些更改然后提交它们,标签不会发生变化,但你的新提交将不属于任何分支,并且将无法访问,除非通过确切的提交哈希才能访问。因此,如果你需要进行更改,比如你要修复旧版本中的错误,那么通常需要创建一个新分支”
tag和分支重名的情况:
如上图,新建一个分支branch-tag同时推送到远程,再次新建一个tag与branch同名叫branch-tag,但是在我们正常推送的时候无法推送,使用推送全部tag的命令git push origin --tags可以正常推送
在远程服务器中可以看到同名的tag和branch
我们正常使用checkout命令来切换分支的时候,在tag和branch同名的情况下正常是切换到分支,如果希望切换到tag则需要使用下面的命令
git checkout refs/tags/branch-tag
一定要杜绝分支和tag同名的情况
转载自:https://juejin.cn/post/7282761240585945140