写点什么

【干货分享】通过命令操作来学习 Git

用户头像
itlemon
关注
发布于: 2020 年 07 月 22 日
【干货分享】通过命令操作来学习Git

Git是一个开源的分布式版本控制系统,可以有效、高速的处理从很小到非常大的项目版本管理。GitLinus Torvalds为了帮助管理Linux内核开发而开发的一个开放源码的版本控制软件。本篇文章将从原始的Git命令出发,学习实际开发过程中最常用、最有效的、最基本的命令,帮助Git初学者快速融入团体开发。由于对Git的基本命令的学习是打基础,而实际的开发过程中大多都是结合开发软件来完成版本控制,所以学习完基本的命令行操作之后,后期推出基于IntelliJ IDEAGit操作学习笔记。


本篇文章将主要介绍以下几点内容:


  • Git 的基本介绍


  • Git 的基本操作


  • Git 的分支操作


  • Git 的更改提交操作


  • 推送至远程仓库


  • 从远程仓库获取


一、Git 的基本介绍

如何理解 Git?Git 是什么?



对于刚刚加入职场的新人来说,被分配到的第一个任务往往都是:从远程仓库把代码拉下来,并熟悉代码吧。如果你以前从来没有接触过Git,那么拉取代码都会有相当大的困难,因为你并不理解怎么拉代码。如果你以前接触过Git,并在学校使用过Git来进行代码的版本控制的话,那么你应该对Git有个基本的认识,至少会拉取代码,添加索引,推送代码到远程仓库等基本操作。其实大家在学习过程中都有一些基本的版本控制思想,那就是在写论文的时候,常常会保存多份文档,分别手动在文件的命名上进行版本控制,如下图所示:

从上图中可以看出,使用这种原始的手动版本控制,可以做到根据时间点来控制文档的版本,甚至可以回退到某个时间点来编写文档,或者是将不同时间点的文档进行合并,但是这种人工方式的版本控制始终具有局限性,整个版本的控制只能由一人来完成,如果多人进行控制,那必定会造成混乱,导致文档内容杂乱不堪,使文档的编辑人员一头雾水。看着这一堆乱七八糟的文档,想保留其中最新的一个版本,删除其他的版本,但是又害怕某天被删除的文档会重新被利用,还不敢删除。

以上的困扰将被Git终结,Git管理的文档(文本文档)允许多人对同一个文档进行修改,各自修改的内容很方便地进行合并,并且可以基于当前内容创建新的分支,在新的分支继续进行修改,最后合并到当前分支上,始终保证文档是最新的。

那么,到底什么是Git?举个传统方式团队协作的例子,Jack在开发项目时,发现某一部分需要John完成,于是他把文件复制了一份发给John,之后继续自己的工作。第二天John将文件传回来,可这时Jack并不知道John对文件做了哪些修改,也无法清楚地分辨出自己做过的变动,除非他们之间事先做过良好清晰的约定或者Jack等待John完成后再继续自己的工作。

使用Git则会极大地简化这一过程。Jack将自己的工作内容上传到远程仓库中,John复制远程仓库内容到本地,之后两个人各自进行自己工作。当John完成工作时,通知Jack拉取项目更新,在拉取过程中, Git会自动合并双方的修改为一体,如果项目成员的修改发生冲突(比如修改同一处),Git允许你手动选择使用什么内容来填充冲突处。这一功能也得益于Git的版本控制机制。在文件内容发生修改时,Git会将发生修改的部分划分为区块进行记录,以区块为单位从而实现自动合并。Git还记录了每次修改的内容节点,在每次提交时,Git生成一个HASH值作为版本号,我们可以通过查看项目历史找到想要的版本,并通过版本号将当前版本回滚到指定版本。下图是本地仓库和中央仓库的示意图:

总之,Git的带来的便捷性远远不止这些,在后期的使用中,你将体会到她的迷人之处。


理解 Git 的工作区和缓存区



  • 工作区(working directory

在新建的文件夹内初始化一个Git仓库之后,那么当前文件夹就可以成为是工作区,工作区内的文件是可以看见的,当然这个工作区不包括初始仓库生成的.git文件夹。


  • 版本库(repository

工作区中有一个隐藏文件夹.git,这个不算工作区,而是Git的版本库。


Git 的版本库里存在很多东西,其中最为重要的是 stage(或者叫index)的暂存区。还有Git为我们自动创建的第一个分支master,以及指向 master 的第一个指针叫 HEAD。

Git中添加,是分两步执行的:

第一步是用git add把文件添加进去,实际上是把文件修改添加到暂存区;

第二步是git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。

可以理解为需要提交的文件统统放在暂存区,然后,一次性提交暂存区的所有修改。


二、Git 的基本操作

阅读上面的内容之后,对Git有了一个基本的了解,但是要更加深刻地了解Git,得通过操作命令来慢慢了解。下面将介绍Git的常见命令,跟着命令来一步一步学习Git


git init——初始化仓库


----

建立一个空文件夹,如果需要使用Git对文件夹内的文本文档进行版本管理,那么就必须进行仓库初始化。进入新建的文件夹中,打开命令行工具,使用git init命令。

出现上面的结果表示当前的仓库初始化成功。初始化成功之后,会在当前文件夹生成一个.git的隐藏文件夹,这个.git文件夹里存储着管理当前目录内容所需的仓库数据。对于文件夹内的文件结构,后期将出相应的文章进行介绍。

Git中,我们将这个目录的内容称为“附属于该仓库的工作树”。“工作树”就是工作区,文件的编辑操作都在工作树中进行,然后记录到仓库中,以此管理文件的历史快照。如果想要将文件恢复到原先的状态,可以从仓库中调取以前的历史快照,在工作树中打开。具的操作方式将在后面详细介绍。


git status——查看仓库的状态



git status是一个非常有用的命令,可以通过这个命令来查看仓库的当前状态。

因为是刚刚初始化的仓库,所以显示正在处于master分支(主分支)下,关于分支内容,后面也会讲到,这里不必过于深究。接下来又显示了没有可以提交的内容,如果新建文件或者拷贝文件到当前工作树,可以使用git add来进行追踪(添加索引)。

现在我们在当前工作树中添加一个新文件README.md作为被管理对象,当然任何文本文件都是可以被管理的,然后再尝试使用git status来观察仓库的状态。

添加完新文件以后,仓库的状态变了,显示的是当前有未被追踪的文件README.md(这里有一个小细节,新添加的README.md还未被Git管理的时候,显示的是红色,后面使用到IntelliJ IDEA的时候,新创建的*.java未被管理之前,文件显示也将是红色,根据颜色也可以判断当前文件的状态)。类似地,只要对Git的工作树或者仓库进行操作,git status命令的显示结果都会发生变化。


git add——向暂存区中添加文件



如果我明只是在工作树中添加或者修改了文件,那么这个文件将不会被git管理,换句话说就是无法进行版本管理,那么添加、修改完文件,需要将其用 Git 管理起来,那么就需要使用到git add命令。git add后面的参数可以是一个文件夹,可以是一个文件,或者是某一类文件(*.java)等。

添加完之后,再次查看仓库状态,又发生了变化,显示的是 Changes to be committed,表示又未提交的修改(这里有一个小细节,未提交修改的文件显示的是绿色,后期在 IntelliJ IDEA 中显示绿色的文件*.java 表示修改后添加了索引但还未提交)。这里还显示了可以使用命令git rm --cached <file>来撤销已添加到暂存区的文件,这里只会移除添加到暂存区的数据,不会影响到工作树中的文件,我们来具体操作一下。

这时候的状态和添加README.md到暂存区之前的状态一模一样。


git commit——保存仓库的历史记录



  • 记录一行提交信息


git commit命令是提交命令,是将已经添加到暂存区的文件提到到本地仓库的历史记录中,通过这些记录,就可以在日后的某一天将此时的文件状态进行恢复。具体的命令是git commit -m "提交信息,可以是任何内容"。参数-m之后是提交的信息,一般都是记录当前修改的内容等。

这就将README.md文件提交到了本地仓库中,提交产生的日志表示新增了一个文件,没有删除任何文件。


  • 记录详细提交信息


有时候我们提交代码的时候仅仅一行提交信息难以描述清楚本次修改的具体内容,所以需要写多行描述信息,那么我们可以直接使用git commit命令来完成多行提交信息的记录。在此之前,我们在刚才测试的文件中添加一些内容,然后重新提交,测试多行描述信息编写的功能,最后测试git commit命令。

对其添加了一些内容之后,必须添加索引后才可以进行提交。

其中在添加多行提交信息的时候,添加的方法和使用vim工具对文本文件进行编辑的方法是一致的,不会使用vim的朋友可以自行学习。


git log——查看提交日志



git log是一个很重要的命令,使用它可以查看当前仓库提交的日志信息,通过日志信息可以很方便查看何人在何时对代码进行了提交和合并,以及提交前后的区别。

使用git log查看刚才我们已经提交的两次内容,如下图所示:

上图中显示了两次提交的详细内容,包括commit id(黄色内容部分),也就是指向相应提交的HASH值,这个值是唯一代表本次提交,使用这个值可以轻松回退到指定的版本。上图上还显示了本次提交的作者和日期时间以及提交的时候编辑的具体提交说明内容。


  • 查看简短的提交日志


有时候并不需要查看过多的提交日志,那么可以使用命令:

git log --pretty=short
复制代码

来查看简短的提交日志,如下图所示:


  • 只显示指定文件或者文件夹提交日志


有时候只想查看单个文件或者指定文件夹的提交日志,可以使用命令

git log 文件名/文件夹名
复制代码

来进行查看,如下图:


  • 查看文件的改动内容


查看文件的具体的改动,可以添加-p参数来查看,具体命令如下:

git log -p [文件名/文件夹名]
复制代码

其中方括号中的内容是可选内容,填写了就表示查看指定文件的改动。

从上图可以看出,最近一次提交相对于前一次提交,增加了一行内容主分支master第一次编写内容,且显示第一次提交是新建的文件。


git diff——查看更改前后的差别



git diff可以查看工作树、暂存区(index)、最新提交(HEAD)之间的差别,可以使用该命令实现查看自己在代码中到底修改了一些什么内容,它也是一个非常重要的常用命令。


  • 查看工作树和暂存区的差别


我们在README.md文件中再添加一行内容,并将其添加到暂存区中,然后再次修改README.md文件,使用git diff命令查看工作树和暂存区之间的差别。

README.md已有的内容为:

可以在后面在再添加一行内容:主分支master第二次编写内容。然后将它添加到暂存区中,然后再次修改README.md文件,添加一行内容:主分支master第三次编写内容。这次不再添加到暂存区,使用命令查看更改前后的差别。

从上图可以看出,工作树中的README.md文件的内容相较于暂存区多了一行。我们再次将README.md文件添加到暂存区中,然后使用命令git diff进行比较,结果没有任何显示,说明工作树中的文件和暂存区中的没有差别。


  • 查看工作树和最新提交的差别


使用命令git diff HEAD就可以查看工作树和最新提交的差别,紧接着上面的操作,我们将暂存区中的最新更改提交到本地仓库中,然后尝试查看工作树和最新提交的差别,结果同样是没有任何差别,这也是很容易想象的。那么我们对工作树中的README.md文件进行修改,不添加到暂存区也不提交,然后在尝试进行查看。

我们修改了工作树中的文件,而没有添加到暂存区,所以使用git diff HEADgit diff命令都是指向了工作树与最新提交的差别。


三、分支操作

使用Git来进行代码托管,主要是为了方便团队和合作开发,并行开发。在并行开发过程中,往往存在多个分支,且各个分支的代码的进度都不一样,开发的内容也不一致,比如develop分支是开发分支,feature是新功能开发分支,master是主分支。对于分支的操作,大多都是新建分支、切换分支、合并分支等。

各个分支完全可以独立开发,等分支作业完成之后,再与主分支mater合并,共同推进项目前进。


  • git branch——查看分支



使用git branch命令可以查看本地分支,如果想查看远程分支,可以在后面加上参数-a

如上图所示,本地分支有且仅有一个master主分支,前面的*表示我们当前正在master分支上进行开发。


  • git checkout -b——创建、切换分支



如果以当前分支master为基础创建新的分支,那么使用命令:

git checkout -b 新分支名称 [master]
复制代码

这是一个创建分支的并切换到新分支的一个命令,后面中括号的内容可以省略,默认是以当前分支为基础,创建新的分支,其中master可以换成远程分支,这样就可以在本地以远程分支为基础创建一个新的分支。仅仅是切换分支,使用git checkout 分支名称即可。上面的创建分支的命令可以换成两行来进行:

git branch 新分支名称git checkout 新分支名称
复制代码

现在使用命令来创建新的分支:

上面的命令是创建的了新的分支并切换到了新的分支上,我们可以使用git branch命令来查看本地分支:

切换到了新的分支,可以通过git status来查看当前分支的状态,因为是基于master创建的分支,所以当前分支也有master分支对应工作树中的最新文档。


  • 特性分支



特性分支一般都是为了完成某项特殊功能的分支,特性分支大多都是从主分支上新建而来,特性分支开发完成之后合并到主分支上。


  • 主干分支



在实际的项目开发中,master往往担任着主干分支的职责,主干分支往往是稳定的分支,可以部署到正式的环境中。


  • git merge——合并分支



我们对之前创建的新分支feature-A中添加部分内容(添加的内容不影响其他分支),然后添加到暂存区,再提交到本地仓库,最后将其合并到主分支中。合并到哪个分支,首先就需要切换到哪个分支上,我们需要切换到master分支上,然后在进行合并。

上面合并使用到的命令是git merge 被合并的分支名,但是这里推荐使用git merge --no-ff 被合并的分支名这个命令,因为后者可以将合并记录到历史中,方便后面使用给git log --graph进行查看。

如果合并完想撤销合并,只要合并后没有进行添加索引和提交,那么可以使用git checkout 文件名或者git checkout .来撤销合并,如果添加了索引,那么可以使用git rm --cached 文件名来撤销添加索引。


  • git log --graph——使用图表方式进行查看日志



git log不仅可以查看提交信息,还可以查看合并等信息,加上参数--graph可以使用图标方式进行查看,在第一次合并,我们使用的是命令是git merge 被合并的分支名,这时候,使用git log --graph命令输出的结果如下图所示:

我们再次修改feature-A分支,再次合并,这次合并使用git merge --no-ff 被合并的分支名这个命令,此时在使用git log --graph命令进行查看:

可以清楚看见,分支合并的过程,这就是在合并时参数--no-ff的作用,--no-ff指的是强行关闭fast-forward方式,fast-forward方式就是当条件允许的时候,git直接把HEAD指针指向合并分支的头,完成合并。属于“快进方式”,不过这种情况如果删除分支,则会丢失分支信息。因为在这个过程中没有创建commit


四、更改提交的操作


  • git reset——会退到历史版本



更改提交操作也是常见的操作,这样可以方便我们的代码回退到某一个时间节点,对于已经提交到本地仓库的分支,如果要撤销提交或回退版本,常见的有三种方式,--mixed--soft--hard。那么,对于这三种方式,到底有什么区别呢?


  • git reset --mixed [commit id或者HEAD]:此为默认方式,不带任何参数的git reset,即时这种方式,它回退到某个版本,只保留源码的修改,回退掉commitindex信息。


  • git reset --soft [commit id或者HEAD]:回退到某个版本,只回退掉了commit的信息,不会恢复到index一级。如果还要提交,直接commit即可。


  • git reset --hard [commit id或者HEAD]:彻底回退到某个版本,本地的源码也会变为上一个版本的内容,此命令慎用!


最常用的是第一种默认方式,风险最小,灵活性更高,以上三种回退方式比较重要,建议牢记。



现在一起来做一个小任务,共同学习一下如何来操作历史版本,首先,我们将工作树、暂存区、最新提交都恢复到feature-A创建之前,然后再基于master分支创建一个fix-B分支,然后切换到fix-B分支并添加部分内容并提交,然后在恢复到feature-A合并之后,然后将fix-B分支合并到主分支上。所以,我们最终需要的结果是:

我们使用命令和commit id切换到指定的历史版本中,提交日志是:测试工作树和最新提交的差别,使用命令:

git reset --hard 316598977bb36a977304dacdf2a48be90f820d3c
复制代码

其中HASH值是与各自的环境相关,使用命令的时候需要更改为自己的HASH值。

因为使用的--hard参数,所以工作树、暂存区、HEAD都切换到了这个历史提交版本,查看README.md内容为:

此时创建一个新的分支fix-B,并在该分支中添加部分内容并提交:

此时各分支的状态是:

我们再将分支恢复到feature-A合并后的状态,也就是如下图所示:

因为此时我们所处的状态是在feature-A与主分支master合并之前,所以要恢复到feature-A,相当于将历史版本向前推进,也就是“穿梭到未来”。由于命令git log只能查看以此时为重点的操作日志,无法查到未来的操作日志,所以我们需要使用另外一个命令来查看,git reflog命令就是我们需要的命令。

上图中黄色的内容都是HASH值的一部分(4 位以上都可以直接参与执行),所以我们切换到master分支来恢复到feature-Amaster合并的时刻。

我们再将fix-B分支合并到主分支master上来:

从上图可知,系统告诉我们自动合并失败了,原因是发生了冲突,需要我们自己手动解决冲突,然后提交结果。我们使用编辑器打开README.md文件,显示的内容如下所示:


=======是合并前与合并后的分界线,我们需要将文件中的内容修改成为我们需要的样子并提交修改后的结果,修改完成之后的结果是:

解决完冲突以后需要添加到暂存区后,完成提交。


  • git commit - -amend——修改提交信息



当我们第一次提交代码的时候,提交信息可能是完全根据我们自己的意愿来写的,但是呢,公司往往对代码的提交信息的格式有要求,比如需要加上 JIRA 号等,所以我们偶尔会需要修改提交信息,那么使用命令git commit --amend就可以了。使用该命令之后,就可以修改上一次提交的信息了,进入编辑器之后就可以修改其中的信息了。

再次通过日志查看结果,提交信息成功修改了:


  • git rebase -i——压缩历史



当提交完代码之后,发现代码的部分注释或者其他不太紧要的内容有些错误,大多数人的做法是撤销本次提交,再次修改后重新提交,其实还有一种比较常见的操作,那就是修改部分错误,重新提交,然后将这次提交包含到前一个提交之中,压缩成一个历史记录,这样的效果就是没有多余的提交记录,看起来就是这个小错误从来没发生过一样。这个小技巧也是很常用的小技巧,我们来测试一下。

基于master分支创建一个新的分支叫feature-C,然后在其中添加一些内容,并认为制造一些单词拼写错误在里面,方便后面修改。

上图中README.md文件最后一行的第一个单词拼写错误,但是我们使用命令git commit -am "创建feature-C分支"进行了一次性的添加索引和提交。

对于上面制造出来的错误,我们在本次进行修改并提交:

由于这个分支进行了两次提交,所以在历史记录中就有两次提交的记录,但是对于第二次提交,健全的历史记录并不需要他们,所以我们希望将这两次提交历史合并成为一次历史,那么使用Git的相关命令轻松可以做到。首先我们查看两次提交的历史记录:

我们使用命令git rebase -i HEAD~2来将两次提交合并,键入命令之后,会打开编辑器,我们将第二次提交的记录前面的pick改成fixup即可,就完成了两次提交记录的合并,后面可以通过查看日志来确认一下。

修改完成之后,就会出现最后一行的温馨提示:

我们再次查看日志:

发现两次提交成功合并成为一次提交了,且这次提交的commit id也不和之前的都一样了。最后我们切换到master分支将feature-C合并上来。


五、推送至远程仓库

以上的操作都是在本地操作的,作为分布型版本管理系统,我们需要将本地仓库的代码推送到远程仓库,方便其他成员协同开发,这里采用的远程仓库是国产代码托管平台码云,至于其他平台,如全球著名的“同性交友网站”——GitHub,操作方法和原理都是一致的。

我们在码云上建立一个公有仓库,由于我们本次示例都是使用的README.md文件,所以在建立仓库的时候,仓库名称需要和本地一致,且不需要使用README.md文件来记录远程仓库的信息,假设读者已经建立好了仓库,且仓库名称和本地仓库名称一致。接下来我们将设置远程仓库中。

git remote add origin git@gitee.com:itlemon/git-test.git
复制代码

其中,git@gitee.com:itlemon/git-test.git来自码云上我们建立的公共仓库,如下图所示:

使用上面的命令之后,Git会自动将git@gitee.com:itlemon/git-test.git远程仓库的名称设置为origin(标识符)。那么我们下次推送或者切换到远程分支的时候加上origin标识符就相当于告诉Git,我们要推送到远程仓库或从远程仓库切分支到本地仓库。

接下来,我们将README.md文件推送至远程仓库,使用命令:

git push -u origin master
复制代码

在第一次推送的时候,可能会遇见各种问题,比如没有权限推送、远程仓库和本地仓库有冲突等等,对于没有权限推送,多数是因为没有创建公钥,那么我们需要在本地Git仓库创建公钥,然后将它设置到码云上,对于第一次推送有冲突,那么可以在上面的命令中添加一个参数-f 来强制推送即可(后期不建议使用强制推送,因为会覆盖远程仓库)。上述命令中-u参数是将origin master分支设置为本地master分支的上游(upstream),这样方便以后拉取代码直接使用命令git push,而无需使用git push origin master,最后推送的效果是:

从上图可以看出,本地的 11 次提交在远程仓库也可以进行查看,如下图所示:


六、从远程仓库获取

对于新入职的员工来说,安装完Git,配置完权限,第一步基本都是被告知需要自己将代码从远程仓库克隆下来,那么如何克隆,那么我们需要使用命令:

git clone git@gitee.com:itlemon/git-test.git
复制代码

使用 git clone 命令之后,我们在本地就建立了一个本地仓库,默认处于master分支,那么我们可以灵活查看远程仓库的各个分支,使用命令:

git branch -a
复制代码

也就是相较于查看本地分支列表,多了一个参数-a,假设我们远程有一个feature-D分支,那么怎么把它从远程仓库切到本地?第一次切换和创建本地分支命令一致,例如:

git checkout -b feature-D origin/feature-D
复制代码

第一次将feature-D分支同步到本地,我们需要建立一个新分支来承载它,通常命名和远程分支的一致。对于以后拉取代码,直接git pull即可,就可以将远程最新的feature-D分支上的代码拉取到本地。如果其中发生了冲突,那么需要手动解决冲突并提交分支,在推送至远程分支,始终保持远程仓库分支是最新的。

有时候我们从一个新的分支不能拉取代码下来,或者使用git branch -a命令不能获取刚刚在远程创建的新分支,那多半是因为本地缓存的远程分支列表没有更新,这时候需要更新一下远程分支列表:

git remote update origin --prune
复制代码

其中origin是远程仓库的标识符,如果你的远程仓库标识符不是origin,需要改成自己的标识符,通常默认都是origin


>掌握了以上的比较常用的命令之后,基本上可以应付大多数工作了,至于在实际开发过程中,我们也许会很少用到命令,但是我个人认为,熟练使用命令将帮助你理解在 IntelliJ IDEA 中各种代码版本操作。以上基本上都是对 git 常见命令的介绍,并采用图文的方式一步一步演示,希望对读者有所帮助。接下来我将继续出一篇结合 IntelliJ IDEA 来使用 Git 的文章,使用其图形化界面来演示如何利用 Git 来高效地和同事合作开发,希望能帮助和我一样的新人快速融入公司团队中。


更多干货分享,欢迎关注我的微信公众号:爪哇论剑(微信号:itlemon)


发布于: 2020 年 07 月 22 日阅读数: 53
用户头像

itlemon

关注

还未添加个人签名 2018.09.30 加入

Java码农一枚。

评论

发布
暂无评论
【干货分享】通过命令操作来学习Git