前言
Git是一个开源的分布式版本控制系统。其核心就在于版本控制。
在实际编码过程中,我们往往会忘记上次对文件的修改内容。若是刚刚修改的还好说,撤销操作即可。但若这是你昨天做的修改并关闭了IDE呢?所以我们需要有一个可以回退版本的工具。
Git还有另一个作用便是多人协作开发——将代码文件上传至云端,partner可以随时下载至自己的电脑,又可以随时做好修改再上传至云端。
我将学习Git分为三块,大家跟着我的步骤做一遍也就会了。
- 配置Git及配置Github
- 本地Git操作
- 远程推送操作
配置Git及配置Github
首先分清楚Git和Github的作用分别是什么。你们可以理解为——Git是运输的小车,Github是仓库,我们将东西装上车,然后让他送到仓库去。
要想使用Git及Github服务,必须得创建Github账号,Github官网:https://github.com/。
创建好账号后,再new一个repository。然后,我们接着配置Git。
我们需要先下载Git工具,这是Git官网:https://git-scm.com,根据自己计算机的系统下载即可。
而后我们需要在本地创建ssh key:
$ ssh-keygen -t rsa -C "your_email@youremail.com"
后面的your_email@youremail.com改为你在github上注册的邮箱,之后会要求确认路径和输入密码,我们这使用默认的一路回车就行。成功的话会在~/下生成.ssh文件夹,进去,打开id_rsa.pub,复制里面的ssh key。
回到github上,点击右上角头像,选择Settings(账户配置),选择左边栏的SSH and GPG keys,点击new SSH key,title随便填,粘贴上你电脑上生成的key。
配置完ssh key后,我们还需要配置username和email:
$ git config --global user.name "your name"
$ git config --global user.email "your_email@youremail.com"
至此,Git的配置和Github的配置已经结束了,你已经有了可用的Git和可用的Github。接下来我们来学习如何进行本地Git操作。
本地Git操作
首先要学习几个概念——工作区,暂存区,版本库。他们之间的关系如下图
这三个区域其实没有什么神秘的,他们都是个文件夹(为树结构)。他们的作用分别为
- 工作区:实际持有的文件夹,即你可以在 我的电脑 里找到的文件夹。用于实际项目开发。
- 暂存区:他就如同一个缓存,可以频繁地将工作区中的文件发送到其中,从而使发送过来的修改后的文件覆盖暂存区中的原文件。
- 版本库:暂存区将他的文件发送到版本库中,用于发送到Github仓库中,作为最新版本。
这三个区域之间的关系及功能搞清楚了,接下来我会先说最重要的Git操作,学会这些操作后就可以直接进行生产活动了。而后,我再陈列其他较重要的的Git操作及更深层的辨析。
首先,创建一个文件夹做为工作区,不过要想这个文件夹成为工作区,需要cd到该文件夹,输入git init
哦!这样其才是工作区,而不是普通的文件夹,切记。
然后,我们在文件夹中随意进行修改,如创建txt文件等。我在文件夹中创建了一个experiment.txt文件,并写入内容。根据上图,我们需要将工作区的文件发送到暂存区,使用git add -A
,该命令的意义为将所有的改动发送到暂存区中。(实际上还有很多种git add方法,但一般来说git add -A已经足够。)
再然后,将暂存区的文件发送到版本库中,使用git commit -m 'comments'
,其中comments为你对这个版本的说明文字。
好了,本地Git操作已经结束了,是不是非常简单?只要三步就好了,我们来复习一遍。
$ git init
$ git add -A
$ git commit -m 'comments'
基础的讲好了,我们接下来进入更深的层次,如果觉得已经够用的,可以跳过这一阶段。
为什么要有暂存区
为什么要有暂存区,我们每次将实际改动的文件直接发送到版本库不就好了?这样也没有任何问题啊,还省了一步。
其实你想的没有错,这样是没有问题的。不过这与软件开发的一大基础原则背道而驰了——单一职责原则。若是没有暂存区,那么版本库就得既负责提交版本,又得负责保存修改,使得Git一点也不优雅。这是两个完全不同的功能,应该由两个模块去分别完成。
分支
引用自https://guides.github.com/activities/hello-world/ 的一句话:
Have you ever saved different versions of a file?
Branches accomplish similar goals in GitHub repositories.
意思就是:你是否为一个文件备份了许多不同版本,在GitHub中分支完成同样的目标。
我们之所以为一个文件备份好多份文件,就是担心我们不备份的话,代码打着打着,突然发现出现了bug,而我们却实在改不过来了,最简单的方法就是回到没有修改前的状态,但我们已经回退不回去了。这样的话,真的就是心态爆炸。
所以我们备份,但是最终我们发现,桌面一团糟。
所以分支就是这样的作用——绝缘特性开发,简而言之就是,给你一个调试bug的分支,同时不影响其他分支。
默认情况下,都有一条主分支——master分支
以下是有关分支的操作:
$ git branch 分支名 //用于创建新的分支,该分支的内容就是当前主分支(master)的内容。
$ git checkout 分支名 //切换到某一分支上
$ git branch -d 分支名 //删除某一分支
$ git merge 分支名 //将该分支合并到当前分支上
要注意一点的是,git checkout 分支名
切换分支后,工作区、暂存区、版本区也都对应的全都改变,即一个分支对应一套独立的工作区、暂存区、版本区,其中我们可以直观地看到的是,切换分支后,工作区(也就是你电脑里的那个文件夹)的文件内容发生了改变。
其中,我要讲的是$ git merge 分支名
这一操作。
分支合并($ git merge 分支名)
master是我们的主分支,而new是我们新建的分支,用于调试代码。而当我们在该分支上调试成功了,我们是不是应该将该分支的内容复制到主分支上?毕竟该分支new仅仅是我们为了调试bug而创建出来的,master才是真正的主分支。
所以分支合并其实就是将在B分支中修改的文件覆盖掉A分支中对应的文件,未修改的文件不覆盖。
那么,又有一个问题来了——如果A分支和B分支都修改了这个文件呢?那么应该听谁的呢?
首先,这个情况我们称为分支合并冲突,其次,当发生分支合并冲突时,他会将两个分支的修改都保存下来,但是要求你得手动修改文件后才能commit提交。
其中,Git用<<<<<<<,=======,>>>>>>>标记出来自不同分支的内容。
回退版本
之前我们说过,Git的核心在于版本控制。那么就自然要求Git得有版本回退的功能。
何为版本?每次commit到版本库后就是一个版本。
首先,第一步——git log
查看历史版本:
第二步——截取commit字样后的任意字数,用作版本标识。如10feb9882453e57。
第三步——git reset --hard 第二步截取的版本标识
回退版本。
就这样,我们就成功地回退版本啦!终于不用担惊受怕自己一不小心脑子犯浑commit了一个错误的版本却无法恢复了。
但由此又出现了一个问题,当你回退到以前的版本,那么在以前的版本后的所有版本就都无法通过git log
查找出来了。
对此,我们通过git reflog
来解决。其意义为查看所有分支的提交操作,其结果也含有版本标识。
小技巧:如果你想要知道各个版本文件内容修改的话,使用git log -p
命令即可。
查看修改内容
有时,我们想要知道还有什么文件需要add/commit,或是自己究竟做了什么样的修改内容,那么我们可以通过git status
和git diff
来查看。
git status
git status
的意义为检索本地仓库的文件更新状态。
这说的太笼统了,执行该命令显示的内容如下:
- 已修改的,未暂存的文件名称
- 已暂存,为提交的文件名称
- 未追踪到的文件
其中,要解释一下未追踪文件的意义:
- 已追踪文件:在仓库之前的版本快照中包含文件的记录,在用户工作一段时间过后,这些文件同样能被追踪到(如文件的修改和删除)。可以理解为,原来就有的文件,非新增文件。
- 未追踪文件:一些文件的新增
git diff
git diff
的意义为查看所有文件进行的具体的修改,添加到暂存区则看不了。
与其对应的是git diff staged
,意义为查看添加至暂存区所有的内容修改
远程推送操作
当你学到这里的时候,离成功就只剩一步之遥了,坚持下去,加油!
现在我们在本地仓库中的版本库中准备好要推送到远程仓库的版本文件了,可是我们现在连要发送到哪一个远程仓库的地址都不知道,所以第一步是——建立本地仓库与远程仓库的连接。
建立连接
建立连接的方法有一下几种:
git remote add 远程主机名 git@github.com:GitHub名称/远程仓库名.git
。这个命令用于将本地仓库和远程仓库连接起来。git clone git@github.com:GitHub名称/远程仓库名.git
。会直接连接远程仓库,并将远程仓库的文件夹克隆到本地目录中。
连接成功了之后,便可以进行push和pull操作了。我们要清楚的是,push和pull也是一种分支合并操作,只不过不再是本地的分支之间进行合并,而是远程分支和本地分支之间的合并。
git push
该命令的意义是将本地版本库的分支推送到远程版本库的对应分支进行合并。
git push
的一般形式为git push <远程主机名> <本地分支名>:<远程分支名>
。如git push origin master:master。
其中,一旦使用上面两种方法连接远程仓库成功,则远程仓库名称为origin。
git pull
该命令的意义是将远程仓库的分支下载至本地,并将其合并到本地对应分支,其实这一个命令是git fetch
和git merge 分支名
的缩写。
git pull
的一般形式为git pull <远程主机名> <本地分支名>:<远程分支名>
。如git pull origin master:master。
好了,这些就是远程推送操作的基本操作。不过,在实际的操作过程中还是会遇到好多好多棘手的问题,大家可以先进行实验,在实验的过程中遇到问题了,再来对比接下来的内容。
接下来,我来为大家列举一些远程推送操作中比较经常出现的问题。注意,接下来我都是基于只远程连接一个远程仓库的情况。
分支追踪
大家可能看到网上的其他教程中,只使用git push
便将版本上传,而我们这样做却往往会报错(但我前面写的第二种连接方式不会报错),为什么呢——因为没有使本地分支跟踪到远程分支。
大家可以先看下这个链接的内容:https://www.cnblogs.com/qianqiannian/p/6008140.html。
其中清楚地说明了,要想省略参数的话,就得使分支之间建立追踪关系。
如何建立追踪关系呢——git branch --set-upstream-to=远程主机名/远程分支名 本地分支名
。如此,便可将本地分支追踪到远程分支,也就可以省去部分参数。
如果想要知道本地仓库和远程仓库之间所有的追踪关系的话,使用git branch -vv
。如果你想要知道远程都有什么分支,使用git branch -r
。
出现! [rejected] master -> master (non-fast-forward)
出现这种情况的原因:
- 如果是push时报的错,则是远程仓库有本地仓库没有的文件或者远程仓库有本地仓库没有的修改内容,则需要先pull下来,再push。
- 如果是pull时报的错,则是本地仓库有远程仓库没有的文件或者本地仓库有远程仓库没有的修改内容,则需要先push上去,再pull。
如果git pull无效的话,就使用git pull origin 远程分支名 --allow-unrelated-histories
便可解决
当push的时候,还会有种报错的情况——分支合并冲突。解决方案是先pull,在本地解决完分支冲突的问题后再push。
小知识点
1.当你在本地新建一个分支,然后直接上传是可行的,会自动在远程仓库中新建一个同名分支,并将内容上传至此。
2.你从远程仓库中pull下来的内容,其工作区,暂存区,版本库都是“干净“的,即不需要你将新增的内容再add到暂存区,再commit到版本库
3.切记,分支合并(包括push和pull)不是一个分支覆盖另一个分支,而是一个分支的修改内容增添到另一个分支。
全文结束。
参考资料
- git add documents:https://www.git-scm.com/docs/git-add
- 为什么要先 git add 才能 git commit ?https://www.zhihu.com/question/19946553
- GitHub简明教程:http://www.runoob.com/w3cnote/git-guide.html
- git status和git diff:https://www.cnblogs.com/kkz-org/p/9313205.html
- git 远程仓库和本地仓库建立连接:https://blog.csdn.net/wowoniuzailushang/article/details/78545971
- Git push:https://www.cnblogs.com/qianqiannian/p/6008140.html
- git中出现“non-fast-forward”errors时的终极解决方案:https://blog.csdn.net/kiddd_fu/article/details/78247290
- 使用Git的Push出现rejected - non-fast-forward错误:https://www.cnblogs.com/xiaoyunxia/p/5975169.html