Android群英传神兵利器读书笔记——第二章:版本控制神器——Git

2023-06-07,,


本人一直是徐医生的真爱粉,由于参加比赛耽误了8天,导致更新得有点慢,大家见谅

2.1 Git的前世今生

Git是什么
Git安装与配置
2.2 创建Git仓库
Git init
Git clone
2.3 提交修改
add&&commit
追加修改
查看代码仓库状态
追溯版本历史
2.4 工作区与暂存区
Git操作区域
2.5 Git回退
checkout&&reset
回退版本
2.6 操作历史
2.7 Git文件操作
git rm
文件暂存
2.8 远程仓库
身份认证
同步协作
Clone远程仓库
2.9 分支管理
创建分支
查看分支
合并分支
删除分支
查看远程分支
推送分支
分支管理思想
2.10 Git图解
2.11 Tag
创建Tag
创建带标签的Tag
查看Tag
删除标签
推送Tag到远程
删除远程Tag
2.12 Git图形化工具
Git for Windows
Github Desktop
Source Tree
Android Studio
2.13 Git学习资料
Git练习


说到Git,就不能不提到一个人——Linus Torvalds

他的一生基本上只干了两件事,一个是Linux,另一个就是Git


Git是用来做版本控制的,像日本公司做事情,每次修改文件都需要在文件第一页新增修改履历,详细记录修改内容,从此维护版本修改记录,如果是多人同时操作,就会发生很多并发问题,可以想象这种做法维护成本有多高,大量时间花费在履历的维护上,同时每次只能一个人进行操作,因此高效的版本控制永远是项目开发的重中之重

为了方便理解,我自己画了个图,意思是:每个人都在同一条主线上工作,如果有分线让其他人同时工作,那么效率将会提高很多

Git是目前世界上最先进的分布式版本控制系统(没有之一),当然把“分布式”三个字去掉也是同样成立的,目前市面上的版本控制工具,主要为集中式的版本控制工具和分布式版本控制工具两种


作为最方便易懂的版本控制方式,集中式的控制最早出现在开发界,它的使用方式与人的合作方式非常类似,即大家都去操作一份文件,如果有人占用则等待。集中式的版本控制工具以SVN为代表,它有一个中央服务器控制着所有的版本管理,其他所有的终端可以对这个中央库进行操作,中央库保证版本的唯一性。

如果中央服务器被毁,整个项目就完蛋
集中式需要不断与服务器交互,如果网络出现故障,操作难以继续下去

因此,它的劣势:

容灾性差
通信频繁

当然,这两个劣势不能完全否定了集中式的版本控制工具,只能说特定场合使用


分布式版本控制的典型就是Git,它跟集中式的最大区别就是它的终端可以获取到中央服务器的完整信息,就好像做了一个完整的镜像。

就算服务器出现问题被毁,各个终端依然有完整的备份
Git的各种操作可以全部发生在本地,只需要最终完成后提交服务器即可,不需要频繁通信


学习Git最好的方式就是忘记SVN、VSS等版本控制工具的控制思想。作为GIt版本控制工具,其核心在于以下几个方面:

分布式:各个Repo都具有完整的镜像,虽然在协作中通常会指定一台中心服务器,但分布式的思想是Git的第一个重要概念
快照:相比SVN,Git每次记录的都是完整的Repo信息,而不是每个版本之间的差异,这也是Git速度快的原因之一
状态区:了解Git的状态区是学习Git的重要步骤,只有掌握了不同状态区中的状态,才能了解Git的核心思想
分支:分支是Git最重要的功能之一,利用好分支可以让Git的使用如虎添翼

个人觉得这段放在这里很难明白作者的意思,应该放在读完这章之后,再来总结,这样才能对号入座


在终端,安装Git

查看Git的版本

如果你之前用过Git,那么通过以下代码可查看当前Git配置信息

当然你也可以通过以下指令显示所有Git项目通用的配置信息

或者通过指定的配置名来获取单独的配置信息

如果还没有对Git配置,那么需要先对Git的Global参数进行基本配置后才能使用,类似于网站注册

配置单个参数
配置多个参数
删除摸个配置
与Linux设计思想一样,我们进入一个Git项目目录,显示所有所有文件

可以发现有个.git的隐藏文件,打开这个文件

这里保存了一个Git像项目的所有配置信息,而个人相关的配置信息都保存在Git的个人配置中


这个功能在Shell命令中很常用,开发者可以根据自己习惯取用别名替代原本比较复杂的指令

通过上面可以使用st来取代status指令,笔者还找到一个关于Git log比较好的Alias

效果如图


版本控制就是为了管理代码,代码就要放在仓库(Repo)中,在Git中创建仓库有两种方式


创建一个仓库
创建clone别人的仓库

进入一个目录,设置成Git的代码仓库

通过上面指令就可以在文件目录下创建成一个Git代码仓库,在创建成功后就会在该目录下生成一个.git隐藏文件夹


Git
clone用于clone一个远程仓库到本地,关于clone后面会继续介绍,不管用哪种方式都会创建一个.git的隐藏文件夹,里面包含所有的版本记录和配置信息,默认不要对这个文件夹修改


为了演示我们在项目创建一个README文件,再把README文件加入到版本控制中去

首先,创建一个README文件,并通过git status指令查看增加新文件,代码仓库的状态变化

在添加文件后,Git追踪到新文件README,并告诉开发者使用git add < file>的方式添加版本控制,最后将add后的文件通过git
commit指令提交到代码仓库,完成一次版本的记录

-m参数指定提交的注释,这时通过git log指令,就可以查看到刚才的提交记录

另外,Git还提供了一个git
shortlog的指令,这条指令可以根据提交者的名字进行分组,显示每个开发者的所有提交commit记录,这适用于在文档中创建发布日志


当开发者提交了一个commit后,发现该commit有错,可以随时对这个commit进行修改,使用git commit –amend指令即可

通过这种方式可以修改commit,而不是通过新的commit来修正前一个错误的commit

记住amend不是修改最近一次commit,而是整个替换掉他,对于Git来说是一个新的commit


里面在空README文件中输入一段文本”this is my readme!”,接下来使用git status指令查看当前代码仓库的版本修改

可以发现,Git提示它检测到了一个修改过的文件”modified: README”,并提醒使用git add\commit进行版本管理

除了使用git status查看文件的修改状态,开发者还可以通过git diff指令查看发生变化文件的具体变化,例如输入git diff
README

git diff指令除了比较指定文件的差异,还可以比较提交节点间的差异,例如使用以下代码比较与上一个commit节点间的差异

同理,通过指定不同的HEAD,例如HEAD^,HEAD^^,上次commit节点、上上次commit节点

除了上面Git自带工具的diff操作,实际上Git还支持使用第三方的diff工具进行diff操作,如Meld、beyond compare等


在项目中一个仓库通常会有非常多次的add、commit过程,这些过程都会被记录下来作为追溯的证据,通过git log指令,显示如下所示

这个Git仓库发生了2次代码提交,每条记录都对应一个commit id,commit id是一个40位的16进制的SHA-1 hash
code用来唯一标记一个commit

同时,我们可以使用gitk指令查看图形化的Log记录,Git会自动将commit串成一条时间线,每个点就代表一个commit,点击这些点就可以看见相应的修改信息

git log参数设置,前面已经提到git log输出结果的颜色搭配,更多指令可以通过git log –help来获取Log指令的详细参数

除了git log指令以外,开发者还可以通过git blame指令追溯一个指定文件的历史修改记录,这里以我另一个项目为例

在Android
Studio中,同样可以找到类似的功能,打开任意一个修改文件,在代码行数的区域内,单击鼠标右键,选择Annotate选项,通过这种方式同样可以找到该文件每一行代码的历史操作人


Git通常是工作在三个区域上的,即工作区、暂存区和历史区,其中,工作区就是开发者平时工作、修改代码的区域;历史区是用来保存各个版本的区域;暂存区则是Git的核心所在

暂存区实际上保存在Git根目录下的.git隐藏文件夹中的一个叫index的文件中,开发者所做的代码提交记录都保存在这个文件中

当我们向Git仓库提交代码时,执行add操作实际上是将修改记录保存到暂存区,例如当我们修改了README文件并执行git
add操作,再通过gitk查看历史,如图

可以发现执行add操作后,Git在本地生成一个记录,但是还没有commit,所以当前HEAD并没有指向最新的修改,修改还保存在暂存区

下面再执行commit操作,继续通过gitk查看历史记录

此时,HEAD已经移到最新的修改上了,也就是说,刚刚commit的内容生成了一个新的commit id,git
commit操作就是将暂存区的内容全部提交,但如果内容不add到暂存区,那么commit也就不会提交修改内容


git checkout指令是用来还原一个代码仓库中的文件的,例如笔者在前面的项目中,继续修改README文件,然后执行git checkout<
file>指令,此时再查看当前代码仓库状态,如图

可以发现在修改文件之后,执行git add指令之前,如果执行checkout指令则会抛弃当前本地的所有修改,恢复到上次最后的提交版本

如果修改文件并执行git add指令后继续修改文件,此时再执行checkout指令,查看代码仓库状态

可以发现在执行add指令将代码提交奥暂存区后,再修改该文件,此时如果继续执行checkout指令,则会将该文件恢复到执行add操作后的初始状态,即恢复add后的所有修改

注意执行git status后显示一句提示:(use “git reset HEAD< file>…” to
unstage),Git告诉开发者可以通过该指令将一个文件移出暂存区,这也是回退的方法,而对于已经commit的提交,如果要进行回退,则可以使用git
reset< last commit SHA><
file>指令,它的原理就是reset掉提交记录,但不修改本地工作区,从而进行新的提交


当代码仓库中有了提交记录后,就可以通过git log指令查看历史记录,如下

在Git中,HEAD表示当前版本,那么上一个版本就是HEAD^,上上一个版本就是HEAD^^,如果往上100个版本就不要这样写了,写成HEAD~100即可

现在我们回退到上个版本

由此可以证明,当前版本已经回退,所以要回退到哪个版本,只要通过HEAD找到对应的版本就可以了,同样你可以写commit
id,也可以以HEAD^、HEAD^^来表示对应版本


如果我们回退之后,发现回退错了,需要恢复到上一个版本,这个时候我们可以通过git reflog指令,查看操作历史,从而获取之前的commit
id进行恢复

我们执行shell的rm指令将README文件删除,接下来执行git status查看当前代码库状态

由此可以发现,通过rm指令确定可以删除一个文件,Git不仅可以监听到增加新文件、删除文件,还可以监听到文件的删除操作,同样通过git
add\commit操作来完成一次新的提交

那么除了从Shell的删除指令rm的方式执行删除操作之外,Git还提供了它的删除指令——git rm

重新创建一个新的README文件,并提交到代码仓库,接下来使用git rm指令删除这个文件

由此可见,git rm指令省去了重新执行git add的操作


这一段,我建议看了分支管理之后再看,作者放在这里并不是很符合逻辑,或者要看的话,理解个懵懵懂懂就行了

这里的暂存不是前文中说到的暂存区,而是指一次备份与恢复操作

举个例子,当前开发者正在dev分支上进行一个新功能的开发,但是开发到一半,测试人员提了一个bug需要解决,这时候开发者通常需要创建一个bug分支来修改这个bug,但是当前dev分支并不是干净的,新功能开发到一半直接从dev上拉分支,代码是不完善的,可能会编译不过,在这种情况下,可以使用git
stash指令将当前修改暂存起来,把修改前的分支作为新的bug分支,而不会带有新修改的代码,等重新切换回dev分支的时候再把代码pop出来,继续开发

例如,你checkout了一个bug分支,修改了bug,使用git
merge指令合并到了master分支并删除了bug分支,重新切换到dev分支,想继续之前的新功能开发,这时候就需要将之前执行git
stash指令暂存的代码pop出来,恢复之前操作

首先,你可以使用git stash list指令查看当前暂存的内容,接下来通过git stash apply指令或者git stash
pop指令进行内容恢复,这两个指令作用是一样的,区别是前者不会删除记录(你也可以使用git stash drop删除记录),而后者会


既然Git是分布式代码仓库,那么开发者肯定是需要多台服务器同时进行协同操作的,个人开发者可以通过Github获取免费的远程Git服务器器,或者使用国内开源中国的OSChina的Git服务器,对于企业用户,则可以铜鼓开源的Gitlab搭建企业级的Git远程服务器

目前大部分的互联网公司都会用Gitlab搭建自己的代码库和代码库管理平台


当本地Git仓库与Git远程仓库进行通信的时候,需要通过SSH进行身份认证


进入.ssh目录,查看是否已经存在id_ras文件和idras.pub文件

由于我没用过,所以不存在,通过下面指令生成这两个文件

执行命令后,按三次Enter键,这里设置的密码就为空了,并且创建了key

该命令生成的id_rsa和id_rsa.pub两个文件就是SSH
Key的秘钥对,id_rsa是私钥用于验证自己的身份,而id_rsa.pub是公钥用在Git远程服务器上表明自己身份


下面需要把生成的SSH的公钥保存到Git远程服务器上,例如Github上,可以在个人配置界面找到添加SSH Key的设置

选择右边列表上的”Add an SSH key”,将生成的id_ras.pub文件内容复制到Key输入框中即可


当开发者在本地建立了Git仓库想与远程Git仓库同步,这样Github、Gitlab上的远程仓库就可以作为本地的备份,或者与其他开发者进行协同工作


首先,在Github创建一个新的Repo(代码仓库)

点击后,Github提示填写相关的Repo信息

最后,点击Create repository按钮,创建好一个代码仓库,创建完毕后,Github提示如何使用这个Repo

在创建好的Repo中,Github会提示如何使用这个Repo,完整的指令都列出来了,开发者只需要按照说明操作就可以了


下面我们提交刚才创建的Git项目

我们再看看Github

README已经提交上去了,回过头来看看添加到远程服务器的代码

这条指令中的origin指的就是远程仓库的名字,你也可以叫别的名字,但是默认远程仓库都叫origin,以便区分

而下面一条指令使用代码把修改推送到远程仓库,代码如下

由于git
push指令加上了-u参数,所以Git不但会把本地的master分支内容推送到远程新的master分支,还会把本地的master分支和远程的master分支关联起来

在Push到远程分支之后,再次Push的时候就不用-u参数了,可以直接使用git push或者git push origin
master指定仓库和分支名将新的修改推送到远程分支

在实际项目中经常会发生这样的协作问题,即开发者A将Push修改到Repo时,开发者B已经将自己的修改Push到了Repo,这时候开发者A在Push时候,Git会提示使用git
pull指令先来获取最新的修改,但这样会在Git历史中留下一个Merge History,这并不是开发者希望的,因此在这种情况下,可以使用git pull
–rebase指令拉取最新修改,该指令的作用是拉取本地代码后,将本地未提交的代码作用到最新版本中,从而避免多余的Merge History


当远程分支上的代码有内容更新时,通过git
pull指令即可拉取最新的代码更新,如果拉取的更新代码与本地没有冲突,那么Git将在本地自动进行代码Merge工作


Github实际上就已经提示开发者如何使用git clone将一个远程代码仓库clone到本地了,如图

在Clone的时候,可以选择Https的方式或者SSH的方式进行Clone操作,但通常情况下都会使用SSH的方式,因为Https的方式会要求账号密码验证,而SSH则是通过对称加密秘钥来验证的,比较方便,而且SSH协议在终端下对Git项目有优化,传输效率较高


举个例子,笔者现在要开发一个新功能,需要大概3个月的时间,但是笔者不能每天都把未完成的代码提交到其他开发者每天都在使用的分支上,这样其他开发者拉取了笔者的代码之后,就可能因此编译不过,而无法正常工作,但是笔者又不能直接新建一个代码仓库,这样仓库太多,很难管理,而使用Git,笔者可以新建一个分支,在这个新的分支上开发新的功能而不会影响其他开发者的工作,新的分支不仅能够备份我的代码,让我能够开发新的功,而且当新功能开发完毕后,可以通过合并分支将整个新功能Merge到其他开发者正在使用的分支中


对于开发者的每次提交,Git都把它们串成一条时间线,这条时间线就是一个独立的分支,不创建其他分支时,默认只有一条时间线,在Git里这个分支叫主分支即master分支,通过gitk指令可以很清楚地看见各个分支的产生、合并情况

由于这里只有笔者一个人操作,所以只创建了一个分支即主分支,通过一下指令,可以创建一个新的分支并切换到该分支上

这里需要注意的是,在前文中笔者也讲到了checkout指令,此checkout指令非彼checkout指令,checkout后面跟分支名才代表分支操作,如果跟的是文件名,则代表恢复操作,另外通过-b参数,可以使Git创建并自动切换到该分支,该命令等价于

如果该分支已经存在,那么直接使用checkout<分支名>就可以切换到该分支,但是在切换时,如果当前分支有过还未提交的修改,则Git是无法切换分支的,此时最好的办法就是通过git
stash指令将修改暂存并恢复到原始版本,再切换分支


通过git branch指令可以列出当前所有本地分支

在当前分支上会多一个*,用来表示当前所处的分支,通过指定-r参数可以列出所有远程分支,或者使用-a参数列举所有本地和远程分支


开发者切换到dev分支后,对内容进行修改,接下来执行add和commit操作,此时开发者再切换到master分支,查看当前修改,你会发现dev分支上之前做的修改在master分支上都没有生效,因为这两个是不同的分支,它们之间是完全独立不受影响的,所以我们切换到master分支,然后Merge分支dev到master主分支中,通过下面代码完成合并工作

在合并分支的时候,经常会发生Merge冲突的问题,这是所有版本控制工具都无法避免的一个问题,Git在合并的时候,会对文件进行自动Merge,如果没有冲突,则自动合并代码,如果有冲突,Git会把冲突的代码都显示在代码中,让开发者删除废弃的代码,最终完成合并操作


在合并分支时,还有一种Rebase操作,它与Merge操作所实现的功能基本是一样的,唯一的区别是,使用Rebase操作后Git时间线会被进行合并,而Nerge操作不会


当一个临时分支使用完毕后,最合适的操作是把这个分支删除,避免过多的分支造成混乱,删除一个不再使用的分支,代码如下

这里有一点注意的是,当一个分支从未进行过合并的时候,如果删除分支会收到提醒信息,如果需要强制删除使用-D参数


当开发者从远程仓库clone代码仓库时,实际上Git自动把本地的master分支和远程的master分支行对应起来了,并且远程仓库的默认名称是origin,我们通过以下指令查看远程分支

或者使用以下指令查看详细信息


要把本地创建的分支同步到远程仓库上,同样是使用git push指令


Git虽然是一个无中央集权的版本控制系统,但在一般开发过程中通常还是会指定一台服务器作为Git版本中央库,同时使用分支对中央库进行版本控制


在Git中央服务器上(origin),都会有一个默认的主分支(master),而一般的开发不会直接在主分支上进行,主分支永远用于打Tag和发布release版本,保证发出去的版本一定是完善的、已验证过的,而且在团队中,也只有Leader以上级别的开发者才有权限将其他分支的代码Merge到主分支,因此开发时,最少会建立一个develop分支,所有的最新开发进展都同步到develop分支


在开发过程中,项目经常有一些需要紧急完成的功能或者需要紧急修复的bug,针对这些打断正常开发流程的事情,同样可以利用分支来进行处理,这些分支称之为功能分支或辅助分支,这些分支的管理与develop分支的处理基本类似,但要注意的是,一旦完成修改应该立刻删除这些分支,保证代码库的干净



Tag的概念类似于branch,区别是branch是可以不断改变、Merge的Tag则不行,Tag可以认为是一个快照、一个记录点,用于记录某个commit点或分支的历史快照,Tag通常打在Master分支上,以保证代码的准确性


创建一个Tag使用以下指令

也可以在指定commit id上创建Tag

这里可以发现commit id并没有写全,实际上通过前6、7位SHA-1 Code,Git就可以查找到相应的id


除了普通的Tag,还可以创建带有注释的Tag,通过-a参数可以指定Tag名,通过-m参数指定注释文字


查看所有的Tag

也可以通过git show< tagname>指令查看指定Tag的详细信息


Tag的删除与分支的删除类似,通过-d参数即可


如果直接使用git push指令是无法将一个Tag推送到远程仓库的,要把一个Tag推送到远程,使用以下指令

或者通过指定–tags参数来推送所有的本地Tag


要删除远程Tag,就必须先删除本地Tag

删除本地Tag后,再重新Push到远程代码仓库,此时指令与推送新建Tag到远程有所不同


Git for Windows:该工具在Windows系统中提供了Git bash终端工具和Git GUI工具,英语教学游戏工具地址https://git-for-windows.github.io/
Github Desktop:Github是使用最广泛的网络Git库,它也提供了自己的Git图形化界面工具,地址https://desktop.github.com/
Source Tree:将Git中很多操作进行了封装,可以很方便地使用图形化界面完成Git的操作,地址https://www.sourcetreeapp.com/
Android Studio:当一个项目被添加到Git Root后,在Android Studio中打开VCS菜单,就可以进行版本控制的管理了


Git权威指南的中文版:http://git-scm.com/book/zh/v1/
Git Community Book学习资料:http://gitbook.liuhui998.com/
WIKI的Git主页:https://git.wiki.kernel.org/index.php/Main_Page/
图解Git最好的网站:http://marklodato.github.io/visual-git-guide/index-zh-cn.html


Git dojo:练习快捷键的网站,将枯燥无味的学习变成有趣的在线游戏https://www.shortcutfoo.com/app/dojos/git
15分钟练习Git:作者虚拟了一个终端,并将Git学习分解成一个个小任务,让开发者在模拟的真实环境中学习https://try.github.io/levels/1/challenges/1
LearnGitBranching:与Learn Git 15 min非常类似,这个网站也模拟了一个Git终端界面


到这里Git这章就结束了,个人感觉徐医生的思路并不是从一个0Git入门的角度出发,一开始我看起来还是比较吃力的,只是有个大概的概念和思路,接着我又去廖雪峰老师的网站读了一遍,简直是顺口溜,因为是先看了徐医生的,懂得概念和思路后,读起来非常简单

学习资料:

廖雪峰老师Git教程传送门
常用Git命令大全思维导图–猴子搬来的救兵Castiel传送门

Android群英传神兵利器读书笔记——第二章:版本控制神器——Git的相关教程结束。

《Android群英传神兵利器读书笔记——第二章:版本控制神器——Git.doc》

下载本文的Word格式文档,以方便收藏与打印。