史上最简单的Git入门教程

1. 版本控制系统简介

1.1 何为版本控制

版本控制最主要的功能就是追踪文件的变更。它将什么时候、什么人更改了文件的什么内容等信息忠实地了已录下来。每一次文件的改变,文件的版本号都将增加。除了记录版本变更外,版本控制的另一个重要功能是并行开发。软件开发往往是多人协同作业,版本控制可以有效地解决版本的同步以及不同开发者之间的开发通信问题,提高协同开发的效率。并行开发中最常见的不同版本软件的错误(Bug)修正问题也可以通过版本控制中分支与合并的方法有效地解决。

下面举一个简单的例子,大家都用过office
word写过文档,在写文档的过程中,可能需要删除原有文档的某一个段落,但又担心该段落之后会再次用到,所以,新建一个文档的副本,然后进行修改。之后,可能会循环重复该流程多次,那么我们的文档库可能就会变成下面的这个样子:

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PoWtFTsB-1616251474892)(media/27eef97b9a22b45b482186e014c3c436.jpeg)]

过了一段时间,你可能需要找回某次删除的内容,但是,你已经记不清修改到底在哪个文档中,只能一个一个的查阅。如果文档的版本特别的多的话,相信这种查找的过程会让你抓狂。而且,这些不同版本的文档,不论何时你都是不该删除的,因为,你不知道未来的哪个时间,你可能就会再次用得到。

最原始的代码管理方式与上面的情况类似。

传统文档/代码管理方式的缺点:

  • 版本管理混乱;

  • 版本回退繁琐;

  • 版本合并困难;

  • … …

    版本控制系统就是为了解决类似于上面的问题而发明的,版本控制系统的基本功能就是记录每一次的修改内容、修改时间、修改人、版本、修改说明等等信息,然后通过工具方便的在不同的版本之间进行切换、合并、回退等等操作。

    所以,为了能够高效、快捷的管理文档或者代码库,很有必要抛弃原来的“手工版本管理”方式,进入自动化的版本管理时代。

1.2 分布式VS集中式

目前,主流的版本控制系统有两种架构:分布式系统和集中式系统,它们分别是什么?有什么区别呢?

  1. 集中式版本控制系统:

    先说集中式版本控制系统,版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TgZrpzaA-1616251474895)(media/ec0b1de73501e448e69dac2b37df70df.jpeg)]

    集中式版本控制系统最大的问题就是必须联网才能工作,遇到带宽不好的情况是,提交、更新较大文件可能会十分的缓慢。而且,集中式版本控制系统容灾性差,万一中心服务器硬盘出现的数据丢失,那后果是很严重的。

  2. 分布式版本控制系统:

    分布式版本控制系统与集中式版本控制系统之间最大的区别就是,分布式版本控制系统不需要“中心服务器”,每一台电脑本地都保留了完整的版本系统,每次代码的提交、回退、分支合并都不需要服务器的参与,本地版本系统就可以搞定。而且,多个不同电脑上的不同版本可以通过网络进行合并、更新。

    分布式版本系统的优点显而易见,首先,它可以完全独立的工作,不需要服务器的参与;其次,它具有很高的安全性,某一台电脑的数据丢失后,可以通过其他电脑进行恢复。

    现实情况下,分布式版本控制系统也需要一个“中心服务器”,但是,该服务器的作用仅限于为数据的交互提供便利性。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AzE1VCAL-1616251474895)(media/6079a201ae304bcf1db739022ecb8a77.jpeg)]

    目前,比较流行的集中式版本控制系统包括SVN、CVS等,这两款系统都是开源免费的,时至今日,仍然有很多人在使用。

    该文档的主角Git属于分布式版本控制系统,其简单、高效,并且功能完备,是目前最为流行的版本控制系统。

2. Git初体验

2.1 Git诞生记

说起Git的起源,那绝对是一个传奇故事。话说,当年Linus自从1991发明了Linux之后,直到2002年都是通过手工的方式(打补丁的方式)来管理Linux代码,随着Linux代码量的指数增长,Linus有些力不从心,出于开源精神和个人癖好(极度讨厌集中式版本控制系统的性能低下),他选择了一个商业版本的分布式版本控制系统BitKeeper。当然,BitKeeper是免费提供给Linus使用的。

直到2005年,两家都相安无事,但是,一起事件激怒了BitKeeper公司,并收回了BitKeeper对于Linux的免费使用权(具体事件是某位Linux内核开发者试图破解BitKeeper的数据协议)。

但,从不服输的Linus没有向BitKeeper公司低头,而是自己用了两周的时间,用C语言开发了一套分布式版本控制系统,那就是Git。一个月之内,Linux的全部代码已经交由Git处理了,Linus真乃大神也啊!

之后,2008年,随着GitHub网站的兴起,git也随之兴盛起来,发展到现在成为最好用的版本管理工具。

2.2 安装Git

Git支持目前所有主流的操作系统,当然,使用Git之前需要首先安装Git,下面分别介绍一下:

  1. Linux系统

    如果你使用的系统为Ubuntu,那么安装Git其实相当的简单,只需一条命令:

    sudo apt-get install git;

  2. Windows系统

    Windows系统的话,可以在Git官网下载合适的版本,就行安装,安装过程十分的简单。

    安装完成之后,回到桌面,右键,选择Git Bash Here,正常情况下,会弹出Git的交互终端;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u3uk6y70-1616251474897)(media/acdc3edae4dee1ee57818021b0589430.png)]
安装完之后,还需要简单的配置,命令如下:

$ git config --global user.name "Your Name" 
$ git config --global user.email "email@example.com" 

上面两条命令主要是为了表明自己的身份信息,这样在代码库中可以清楚的查看每一条提交的相关人员信息。

注:git
config命令的–global参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。

2.3 创建Git版本库

版本库,英文名:repository,物理意义上,它其实就是一个目录,我们把需要控制管理的代码、文档放入到该目录下,然后,该目录下的所有文件的添加、删除、修改、回退等信息都可以管理起来。

创建一个Git版本库十分的简单,只需要两步:

  1. 选择一个合适的地方,创建一个空目录:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MGJI90mN-1616251474898)(media/5719f8428bb3f8ea51e9b6541ef92cce.png)]
注意:如果你使用Windows系统,为了避免遇到各种莫名其妙的问题,请确保目录名(包括父目录)不包含中文。

  1. 执行git init创建Git版本库:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Htxm5nth-1616251474899)(media/9c10e03426c68e56ca7262f7937ae81a.png)]
    注意:学习Git时,尽量不要使用项目代码做实验,否则,操作不当会出现数据丢失。

    简单两步,一个Git库就创建好了,下面我们往库里加些文件:
    在这里插入图片描述
    上面命令,创建一个readme.txt文件,并添加了一些内容。

    下面说一下,如何将readme.txt文件加入到版本库中,分为两步:

  2. 使用git add 命令将readme.txt文件加入到Git库中:

$ git add readme.txt 

如果有多个文件需要添加到Git库中的话,可以多次调用git add命令。

  1. 使用git commit命令,将文件提交到仓库中:
$ git commit -m "wrote a readme file"
[master (root-commit) eaadf4e] wrote a readme file
1 file changed, 2 insertions(+)
create mode 100644 readme.txt

git commit
命令的-m选项,用于表示本次提交的说明信息,说明信息最好统一格式,这样对于后续的Bug管理、版本信息追踪都会大有好处。

3. Git本地管理

3.1 提交修改

之前我们已经添加了readme.txt文档,现在进一步开始修改该文档,并学习一下git
status命令的使用方式。

打开readme.txt文档,在第一行添加单词distributed,如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GVnsZkBO-1616251474901)(media/95a93a2688ad0339e8f1db47fbeefbd6.png)]
现在尝试使用git status,查看仓库的状态:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TReUbxMX-1616251474901)(media/dfca0f83e234ae973261656dd507f025.png)]
通过git
status可以让我们随时掌握git仓库的状态,上面的输出表示readme.txt已经修改,但是,还未暂存到git仓库。

我们可以通过git diff查看当前的修改内容,输出格式为linux内核补丁所用的格式:
在这里插入图片描述
现在,可以提交我们的修改了,所使用的命令还是git add和git
commit。首先,使用git add添加readme.txt文件:

$ git add readme.txt 

使用git status查看一下版本库的当前状态:
>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NBZ2Qr3X-1616251474902)(media/3a4c7057c506b3b5dcd664388e82f05a.png)]
可以准备提交的文件包括:readme.txt,下一步,git commit提交本次的修改。
![> [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CFEmWlFM-1616251474903)(media/04789ebe7e7808ae7341d292e0eb7ad6.png)]](https://img-blog.csdnimg.cn/20210320225241366.png

再次,运行git
status命令,可以看到版本库已经没有需要提交的内容了,工作目录是clean的。

如果,git commit完之后,发现提交说明写的有问题,可以通过git commit --amend命令,修改最近一次的提交说明:

  • 使用git log查看最近的提交记录:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gEKdVhbc-1616251474903)(media/a4894ae42f6c077523f545fd93aeaa76.png)]

  • 执行git commit --amend命令:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sHWWB2Xg-1616251474904)(media/2c821c1d0ebf27644919a78c40d53891.png)]

  • 修改提交记录为: “add distributed --amend”:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LC4isCRH-1616251474905)(media/9f53dda7a9fa83529433fe87f0f804ce.png)]

  • 再次通过git log查看提交记录:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-InrXO6Wy-1616251474905)(media/77aace42047417bca71393fbd5c576db.png)]

    根据上图可知,最后一次的提交记录已经修改完成。

3.2 版本回退

现在,我们已经学会了如何向版本库中添加文件,并且学会了如何提交修改。下面继续修改readme.txt文件:

Git is a distributed version control system.
Git is free software distributed under the GPL.

然后,提交修改:
>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qlbjy5PM-1616251474906)(media/395fd4a366676e5fac6ce0c0b8adde60.png)]

实际产品开发过程中,我们需要养成一个习惯,那就是代码/文档修改到一定程度时,要将修改的内容提交到版本库。这样,一旦后续代码/文档修改出了问题,或者误删了某些文件,你还可以通过最近的一次commit进行恢复,这个恢复的过程就是版本回退。下面演示一下,如何进行版本回退操作。

  1. 首先,通过git log查看一下版本库目前几次的提交情况:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Skh7TvWy-1616251474907)(media/9560f3b3d266428377a09bf622921eb0.png)]

    可以看到,目前存在三个commit记录。可以通过–pretty=oneline参数,查看简化版的提交记录信息:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ry7TOZoo-1616251474907)(media/6519b1e5d2b64a19dd024ac047c56b9b.png)]

    注意:需要友情提示的是,你看到的一大串类似1094adb…的是commit
    id(版本号)
    ,和SVN不一样,Git的commit
    id不是1,2,3……递增的数字,而是一个SHA1计算出来的一个非常大的数字,用十六进制表示,而且你看到的commit
    id和我的肯定不一样,以你自己的为准。为什么commit
    id需要用这么一大串数字表示呢?因为Git是分布式的版本控制系统,后面我们还要研究多人在同一个版本库里工作,如果大家都用1,2,3……作为版本号,那肯定就冲突了。

  2. 如果,由于某种原因你想将版本库回退到“add distributed”那次提交版本的话,该如何操作呢?

  • 首先,Git必须知道当前版本是哪一个版本,在Git中,用HEAD表示当前版本,HEAD^表示上一个版本,HEAD^^表示上上一个版本,HEAD~100表示往上100版本。好了,那么想回退到上一个版本,执行如下的命令就行了:
$ git reset --hard HEAD^

在这里插入图片描述

看一下readme.txt的内容,果然还原了。
在这里插入图片描述

  • 但是,如果过了一段时间,突然发现需要返回之前的版本,那该怎么操作?其实,只要能找到之前版本的commit
    id,就可以通过git reset命令就行回退。Git提供了git
    reflog用于记录每次的命令,那么就可以通过git reflog找到之前版本的commit
    id了。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zf5xVKGy-1616251474909)(media/736f0a5b96cbd7cf4c9171143a433321.png)]

    红框标记的就是最后一次的版本提交记录,最开始的那串数字就是commit
    id。好了,执行如下命令返回之前的版本:

$ git reset --hard 3b45c71

在这里插入图片描述

通过git log可以看到,版本已经复原了,查看一下文件内容是否复原:

在这里插入图片描述

  • Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD从指向append
    GPL:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ah69Mlji-1616251474911)(media/addf56ea73cffdc935d191e46935c40d.jpeg)]

    改为指向add distributed:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bfzUOTmK-1616251474912)(media/a5443d7b51883b4136a04cc27831c6a7.jpeg)]

3.3 工作区和暂存区

在使用Git管理版本时,经常会听到工作区和暂存区这两个概念,理解工作区和暂存区对于理解Git的很多操作十分有帮助,下面分表介绍一下这两个概念。

  1. 工作区

    工作区即我们实际看到的目录,比如learngit就是一个工作区。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wKoSN9ZL-1616251474913)(media/ab3a096f9901a50b6c29f5193ecf1740.png)]

  2. 暂存区

    工作区里有一个隐藏目录.git,该目录不是工作区,而是Git的版本库。

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hHoMal7U-1616251474914)(media/701e02bf0b2d71ab51b7e2b6aa973d52.jpeg)]

分支和HEAD的概念我们以后再讲。

前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:

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

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

因为我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,git
commit就是往master分支上提交更改。

你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。

下面通过一个简单的练习来更加深入的了解暂存区的概念。

打开readme.txt,增加一行内容:

Git **is** a distributed version control system.

Git **is** free software distributed under the GPL.

Git has a mutable index called stage.

然后,在工作区,新建一个文件LICENSE,文件内容随便。

首先,用git status查看一下版本库的当前状态:

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bZBbaFHs-1616251474914)(media/514b6e267fc92f83668a14b684d67870.png)]

Git非常清楚地告诉我们,readme.txt被修改了,而LICENSE还从来没有被添加过,所以它的状态是Untracked。

现在,使用两次命令git add,把readme.txt和LICENSE都添加后,用git status再查看一下:

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OilFNe2X-1616251474917)(media/d0206049aca519b8f948a09008391785.png)]

现在,暂存区的状态就变成这样了:
>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G3lNl0N1-1616251474918)(media/3140921b12cc7d9a949076b2bb80d637.jpeg)]

所以,git add命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit就可以一次性把暂存区的所有修改提交到分支。

$ git commit -m "understand how stage works"

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sOFWttrX-1616251474919)(media/ebe27807687a36257198f25ae120672d.png)]

现在版本库变成了这样,暂存区就没有任何内容了:

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pKWA3Kkf-1616251474920)(media/f6ddf40f921f9953b6f84d107a11f689.jpeg)]

  1. git diff比较域

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7diZF38I-1616251474921)(media/07677f9a98e25f2dd66e952a2083d591.png)]
由上图可知,git diff后接不同的参数所表示的比较域是不同的:git diff 是只比较比较工作区和暂存区(最后一次add)的区别,git diff --cached是只比较暂存区和版本库的区别,git diff HEAD – filename 是只比较工作区和版本库(最后一次commit)的区别。三种比较各对应不同命令。

3.4 管理修改

Git之所以比其他版本控制系统性能优秀,是应为它跟踪并管理的是修改,即每次版本之间的差异,而不是简单的文件。

修改可以是新增了一行、删除了一行、增加了一个字符或者新增了一个文件等等,统统都算作修改。

下面通过简单的实例来证明Git是如何管理修改的。

  1. 打开readme.txt文件,新增一行内容:
$ cat readme.txt

Git **is** a distributed version control system.

Git **is** free software distributed under the GPL.

Git has a mutable index called stage.

Git tracks changes.
  1. 然后,使用git add添加本次修改:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZK5rVKMr-1616251474922)(media/9cc87f64910c06a5b72678676924516e.png)]

  1. 然后,再次修改readme.txt文件:
  $ cat readme.txt

Git **is** a distributed version control system.

Git **is** free software distributed under the GPL.

Git has a mutable index called stage.

Git tracks changes of files.

提交,然后查看状态:
在这里插入图片描述
可以看到第二次修改的内容并未提交。

那就证明了Git管理的是修改,当你用git add命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区,所以,git commit只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。

通过git diff HEAD – readme.txt,可以查看当前最新版本和工作区的区别:

在这里插入图片描述
可见,修改并未提交到版本库。再次使用git add、git commit命令经第二次的修改提交到版本库。

3.5 撤销修改

实际的项目开发过程中,不可避免的会犯错,例如,Bug改错了位置、代码注释写错了等等。出现了错误的修改之后,就要及时的撤销修改,下面分别介绍不同场景下的撤销操作。

  1. 改乱了工作区的某些文件的内容,想直接丢弃这些修改。

    打开readme.txt,添加一行内容:

Git is a distributed version control system.

Git is free software distributed under the GPL.

Git has a mutable index called stage.

Git tracks changes of files.

Git add stupid chagnes.

使用git status命令查看一下当前版本库的状态:

在这里插入图片描述

你会发现git提示可以通过:git checkout --<file>命令,丢弃工作区修改的内容。那我们执行该命令:

git checkout – readme.txt
在这里插入图片描述

再次运行git status可以看到工作区已经clean了,并且readme.txt已经恢复到了版本库的最新版本。

  1. 不但改乱了某些文件的内容,而且还将修改提交到了暂存区。

    在场景1的基础上,我们使用git add将readme.txt提交到了暂存区,使用git
    status查看当前版本库的状态:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jUF09mZx-1616251474926)(media/897ccdb7dcdbdb600976cb3d426b0b9c.png)]

    同样的,Git还是贴心的提示可以使用git reset HEAD <file>命令,将暂存区的修改转移到未暂存状态(即工作区),我们运行一下该命令,并使用git status查看一下当前状态:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FqLkUrmv-1616251474927)(media/d47b5865586507ad625f2407e47d54eb.png)]
可以看到,readme.txt文件的状态又回到了场景1的状态,那么通过场景1所介绍的方法可以丢弃相应的修改。
在这里插入图片描述

  1. 已经将错误修改提交到了本地的版本库。

    如果只是将错误的修改内容提交到了本地的版本库,那完全可以使用3.2节介绍的方法进行版本回退。但,如修改已经提交到远程版本库的话,那谁也无力回天了。

3.6 删除文件

实际工作中,删除某些文件也是比较常见的操作,下面实例演示一下。

首先,在工作区添加一个文件,并且将其添加到版本库中。

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LOfUXVz4-1616251474930)(media/5accbb937b8ffd95efd856ea5e25b938.png)]

平时,我们习惯于使用rm或者直接删除文件,那么现在将其删除,并查看当前版本库的状态:

在这里插入图片描述

git提示其已经感知到了工作区的test.txt被删除,但版本库中的文件还未删除,git提出两种操作提示,下面分别演示一下:

  1. 使用git add/rm <file>更新修改到暂存区以备commit。

q

  1. 使用git checkout – <file>丢弃工作区的修改,即还原刚才删除的test.txt文件。

在这里插入图片描述

3.7 查看提交历史

在提交了若干更新,又或者克隆了某个项目之后,你也许想回顾下提交历史。 完成这个任务最简单而又有效的工具是 git log 命令。
我们使用一个非常简单的 “simplegit” 项目作为示例。 运行下面的命令获取该项目:

$ git clone https://github.com/schacon/simplegit-progit

当你在此项目中运行 git log 命令时,可以看到下面的输出:

$ git log
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Mon Mar 17 21:52:11 2008 -0700

    changed the version number

commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 16:40:33 2008 -0700

    removed unnecessary test

commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 10:31:28 2008 -0700

    first commit

不传入任何参数的默认情况下,git log 会按时间先后顺序列出所有的提交,最近的更新排在最上面。 正如你所看到的,这个命令会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间以及提交说明。

git log 有许多选项可以帮助你搜寻你所要找的提交, 下面我们会介绍几个最常用的选项。

其中一个比较有用的选项是 -p 或 --patch ,它会显示每次提交所引入的差异(按 补丁 的格式输出)。 你也可以限制显示的日志条目数量,例如使用 -2 选项来只显示最近的两次提交:

$ git log -p -2
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Mon Mar 17 21:52:11 2008 -0700

    changed the version number

diff --git a/Rakefile b/Rakefile
index a874b73..8f94139 100644
--- a/Rakefile
+++ b/Rakefile
@@ -5,7 +5,7 @@ require 'rake/gempackagetask'
 spec = Gem::Specification.new do |s|
     s.platform  =   Gem::Platform::RUBY
     s.name      =   "simplegit"
-    s.version   =   "0.1.0"
+    s.version   =   "0.1.1"
     s.author    =   "Scott Chacon"
     s.email     =   "schacon@gee-mail.com"
     s.summary   =   "A simple gem for using Git in Ruby code."

commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 16:40:33 2008 -0700

    removed unnecessary test

diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index a0a60ae..47c6340 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -18,8 +18,3 @@ class SimpleGit
     end

 end
-
-if $0 == __FILE__
-  git = SimpleGit.new
-  puts git.show
-end

该选项除了显示基本信息之外,还附带了每次提交的变化。 当进行代码审查,或者快速浏览某个搭档的提交所带来的变化的时候,这个参数就非常有用了。 你也可以为 git log 附带一系列的总结性选项。 比如你想看到每次提交的简略统计信息,可以使用 –stat 选项

$ git log --stat
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Mon Mar 17 21:52:11 2008 -0700

    changed the version number

 Rakefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 16:40:33 2008 -0700

    removed unnecessary test

 lib/simplegit.rb | 5 -----
 1 file changed, 5 deletions(-)

commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 10:31:28 2008 -0700

    first commit

 README           |  6 ++++++
 Rakefile         | 23 +++++++++++++++++++++++
 lib/simplegit.rb | 25 +++++++++++++++++++++++++
 3 files changed, 54 insertions(+)

正如你所看到的,–stat 选项在每次提交的下面列出所有被修改过的文件、有多少文件被修改了以及被修改过的文件的哪些行被移除或是添加了。 在每次提交的最后还有一个总结。

另一个非常有用的选项是 –pretty。 这个选项可以使用不同于默认格式的方式展示提交历史。 这个选项有一些内建的子选项供你使用。 比如 oneline 会将每个提交放在一行显示,在浏览大量的提交时非常有用。 另外还有 short,full 和 fuller 选项,它们展示信息的格式基本一致,但是详尽程度不一:

$ git log --pretty=oneline
ca82a6dff817ec66f44342007202690a93763949 changed the version number
085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 removed unnecessary test
a11bef06a3f659402fe7563abf99ad00de2209e6 first commit

4. 远程仓库

4.1 GitHub初体验

到目前为止,我们已经学会了很多的关于Git的基本操作知识。但是,这些功能与之前的SVN之类的集中式版本控制系统相比也没有太大的差别。

下面介绍Git杀手级功能之一:远程仓库。

Git是分布式版本控制系统,同一个Git仓库,可以分布到不同的机器上。怎么分布呢?最早,肯定只有一台机器有一个原始版本库,此后,别的机器可以“克隆”这个原始版本库,而且每台机器的版本库其实都是一样的,并没有主次之分。

实际工作场景往往是这样的,找一台电脑作为服务器,其他每个人都从这个“服务器”仓库克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。“服务器”在这个过程中充当角色,仅仅作为数据共享的中介。

Git服务器可以自己搭建,现阶段为了学习git,完全没有必要小题大做。我们可以依托于GitHub,这个世界最著名的Git仓库托管网站,来学习远程仓库的使用方式。

只要在GitHub注册一个账号,就可以开始使用GitHub提供的服务了。但,由于你的本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以,需要一点设置:

  1. 创建SSH
    Key。在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git
    Bash),创建SSH Key:
$ ssh-keygen -t rsa -C"youremail@example.com"

在这里插入图片描述

你需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个Key也不是用于军事目的,所以也无需设置密码。

如果一切顺利的话,可以在用户主目录里找到.ssh目录,里面有id_rsa和id_rsa.pub两个文件,这两个就是SSH
Key的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。

  1. 登录GitHub,按照下面步骤将公钥添加到自己的GitHub账号中。

    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

在步骤4中填写上有意义的Title,并将/c/Users/Administrator/.sshid_rsa.pub文件的公钥填写步骤5所示的文本框中。

在这里插入图片描述

点击,Add SSH Key,可以看到已经成功将Key添加到GitHub中账号中。

在这里插入图片描述

  1. 为什么GitHub需要SSH
    Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。

    当然,GitHub允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。

4.2 添加远程仓库

现在我们本地已经创建了一个Git仓库learngit,并且已经开通了GitHub账号,并且已经配置好了SSH
Key信息。那么,接下来在GitHub创建一个Git仓库,并把本地库和GitHub上的仓库进行关联,实现同步操作,这样GitHub上的仓库既可以地库的备份,世界上的其他人也可以通过该库与你进行协作。

GitHub上创建一个仓库十分的简单,只需几步就可完成:

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wDvCCHWi-1616251474937)(media/17cbc1ab91a0da6e1546f8f23e6cfbbd.png)]

repository name为:learngit,点击Create repository创建仓库。

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FxfvMhl3-1616251474937)(media/cc18f85d15a00a51b94814fee4615805.png)]

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GBxcbFSQ-1616251474938)(media/1635b5db9bae19842c5727c7e556951a.png)]

目前,在GitHub上的这个learngit仓库还是空的,GitHub告诉我们,可以从这个仓库克隆出新的仓库,也可以把一个已有的本地仓库与之关联,然后,把本地仓库的内容推送到GitHub仓库。

按照GitHub的提示,在本地learngit仓库下运行如下命令:

$ git remote add origin https://github.com/flyunix/learngit.git

标黄的为GitHub的账号,实际使用时需要替换成自己的账号名。添加后,远程库的名字就是origin,这是Git默认的叫法,也可以改成别的,但是origin这个名字一看就知道是远程库。

将本地库同步到远程库:

$ git push -u origin master

由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。之后会提示GitHub登录,输入账号、密码进行登录:

在这里插入图片描述

下图表示本地库和远程库已经同步完成。

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mCssp9E0-1616251474939)(media/dc14842906465d0d2df4e23a4baa31fc.png)]

推送成功后,可以立刻在GitHub页面中看到远程库的内容已经和本地一模一样:

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lwID6umA-1616251474939)(media/67816b03778c10ed67f7b751b8444564.png)]

从现在起,只要本地作了提交,就可以通过命令:

$ git push origin master

把本地master分支的最新修改推送至GitHub。

4.3 克隆远程仓库

4.2节讲了本地库和GitHub上的远程库如何进行关联。本节介绍如何克隆一个GitHub上的远程库。

  1. 登录GitHub新创建一个版本库,名为gitskills。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5B0Nor5W-1616251474940)(media/2a4618429c07d8f975a4e815e2a42485.png)]

注意勾选上**Initialize this repository with a README,**这样版本库中就会自动添加一个README文件。

在这里插入图片描述

现在远程库准备好了,我们可以使用git clone克隆一个本地库:

$git clone git@github.com:flyunix/gitskills.git

在这里插入图片描述
注意把Git库的地址换成你自己的,然后进入gitskills目录看看,已经有README.md文件了:
在这里插入图片描述

其实,GtiHub给出的地址不止一个,还可以通过https://github.com/flyunix/gitskills.git这样的地址。实际上,Git支持多种协议,默认的git://使用ssh,但也可以使用https等其他协议。使用https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh协议而只能用https。

4.4 Git服务器搭建、使用

考虑到使用GitHub进行远程仓库实验可能存在困难,我们可以自己搭建一个局域网下的Git服务器。目前,市面上存在很多款Git服务器软件,但是,最为快速的搭建一台Git服务器方式就是:基于安装了ssh和git的Linux系统,尤其是Ubuntu或者Debian系统,只需几个简单的apt命令就可以安装完成。

4.4.1 远程登录Git服务器

目前,我已经准备好了一个一台Ubuntu虚拟机,可以使用putty通过ssh连接到该虚拟机。虚拟机的基本配置信息如下:

  • 访问地址:192.168.2.35:6666

  • ssh登录账号:本人姓名的拼音首字母缩写,如lhl

  • ssh登录密码:123(所有账户一律相同)

    例如,通过Putty登录lhl。
    在这里插入图片描述

    配置好ssh服务器的IP地址、端口号之后,点击"Open"。

在这里插入图片描述
输入账号:lhl,回车,输入密码:123,回车,如下图登录成功。
在这里插入图片描述

4.4.2 搭建远程仓库

本节介绍如何创建一个git仓库。

  1. 首先,通过ssh登录自己的账户。

  2. 收集所有需要登录的用户的公钥,就是他们自己的id_rsa.pub文件,id_rsa.pub文件的产生可以参考,把所有公钥导入到/home/git/.ssh/authorized_keys文件里(该文件首次需要创建),一行一个。

  3. 选定一个目录作为Git仓库,假定是~/srv/learngit.git,在~/srv目录下输入命令:

git init --bare learngit.git

Git就会创建一个裸仓库,裸仓库没有工作区,因为服务器上的Git仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区,并且服务器上的Git仓库通常都以.git结尾。然后,把owner改为自己的账户名,例如lhl:

chown -R lhl:lhllearngit.git

这样,远程仓库就创建好了。

参照添加远程仓库所讲的将本地的learngit仓库与刚创建好的远程仓库进行关联,需要注意的是由于Git服务器的端口号为6666,并非标准ssh 22,所以,关联时的命令略有差别,注意将标黄用户名改成自己的:

$ git remote add origin ssh://lhl@192.168.2.35:6666/home/lhl/srv/learngit.git

4.4.3 克隆远程仓库

现在,我们自己创建了自己的版本仓库,可以参照克隆远程仓库的方式,将Git服务器上的仓库克隆到本地。需要注意的是由于Git服务器的端口号为6666,并非标准ssh 22,所以,克隆时的命令略有差别:

$git clone ssh://lhl@192.168.2.35:6666/home/lhl/srv/learngit.git

5. Git分支管理

5.1 为何需要分支

分支是版本管理系统中的重要概念,一个版本库中的不同分支互不干扰,完全独立,只到分支合并那一天。那么,实际工作中,分支的作用到底是什么呢?

假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。

现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。

但Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。

5.2 分支的创建和合并

版本回退里,你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支。HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是当前分支。

一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点:

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-keHRepmN-1616251474944)(media/a1c29dbcd27b8b5be9899afbb2bb0b24.png)]

每次提交,master分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长:

当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:
在这里插入图片描述

由上图可以看出,Git创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化。

不过,从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:
在这里插入图片描述

假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并:

在这里插入图片描述

所以Git合并分支也很快!就改改指针,工作区内容也不变!

合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支:

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5eUTvWUh-1616251474948)(media/2bdaaedcea49b643d9054b364fcc9d7b.png)]

下面实例演示一下具体的命令操作。

首先,创建dev分支,然后自动切换到该分支:

在这里插入图片描述

git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:
在这里插入图片描述

然后,通过git branch查看版本库中的所有分支,其中当前分支前面标有*。

在这里插入图片描述
现在,我们在dev分支上做一些修改,例如,readme.txt添加一行:
在这里插入图片描述
在dev分支上提交本次修改:

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ueyW3k7I-1616251474950)(media/1379a2e03ff631b70becd945cf9a97f9.png)]

现在dev分支上的工作完成了,切换到master分支,查看readme.txt文档的内容:

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5POzRhT3-1616251474950)(media/ee9c2153f734d8f903344468c250adec.png)]

可以看到,刚才在dev分支上所做的修改,并未影响到master分支。现在,我们将master和dev分支进行合并:

在这里插入图片描述
git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。

注意到上面的Fast-forward信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。

当然,也不是每次合并都能Fast-forward,我们后面会讲其他方式的合并。

合并之后,就可以放心的将dev分支删除了:

在这里插入图片描述
通过git branch也可以发现现在系统中,只剩下一个分支了。

因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。

5.3 冲突解决

在实际开发过程中,基于git的代码开发模式可能是有多个人在多个不同的分支上工作,而分支合并并不总是一帆风顺的,实际情况总是要处理各种各样冲突。

下面举一个简单的例子,演示一下如何解决开发过程中遇到的版本库冲突的问题。

  1. 准备新分支,feature1:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CKihIYU7-1616251474952)(media/f30181a73e3872f5ba7e44b80ff5f84c.png)]

    修改readme.txt最后一行,改为:

Creating a **new** branch is quick **AND** simple

在feature1上提交修改:
在这里插入图片描述

  1. 切换到master分支:

在这里插入图片描述

在master分支上把readme.txt文件的最后一行改为:
Creating a **new** branch is quick & simple

在master分支上提交本次修改:

在这里插入图片描述

现在,master分支和feature1分支各自都分别有新的提交,变成了这样:

在这里插入图片描述

这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看:

在这里插入图片描述

git提示合并存在冲突,需要手动解决,然后重新提交。git
status可以告诉我们存在冲突的文件。
在这里插入图片描述

直接查看readme.txt文件:

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UnBd8n2K-1616251474957)(media/56e2742060f5fb0ac642fd68ae68084c.png)]

Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,我们修改如下后保存:

Creating a new branch is quick and simple.

现在,master分支和feature1分支变成了下图所示:

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UhBqmujk-1616251474957)(media/1d4f9895cb768f68ecfc080432477ae9.png)]

用带参数的git log也可以看到分支的合并情况:

$ git log --graph --pretty=oneline --abbrev-commit

在这里插入图片描述
最后,删除featuer1分支。
在这里插入图片描述

5.4 分支管理策略

通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。

下面我们实战一下–no-ff方式的git merge:

首先,仍然创建并切换dev分支:

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PSfBvfoc-1616251474959)(media/35c693ff92c54fce22b314a312e6f377.png)]
修改readme.txt文件,并提交一个新的commit:

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xt0PLNIZ-1616251474959)(media/c65555777344b3bb9a9bcacc11f2bd6b.png)]

准备合并dev分支,请注意–no-ff参数,表示禁用Fast forward:

$ git merge --no-ff -m "merge with no-ff" dev

因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去。

合并后,我们用git log看看分支历史:

$ git log *--graph --pretty=oneline --abbrev-commit*

在这里插入图片描述

可以看到,不使用Fast forward模式,merge后就像这样:
在这里插入图片描述

在实际开发中,我们应该按照几个基本原则进行分支管理:

首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;

那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;

你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。

所以,团队合作的分支看起来就像这样:
在这里插入图片描述

5.5 Bug分支

软件开发中,bug就像家常便饭一样。有了bug就需要修复,在Git中,由于分支是如此的强大,所以,每个bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。

当你接到一个修复一个代号101的bug的任务时,很自然地,你想创建一个分支issue-101来修复它,但是,等等,当前正在dev上进行的工作还没有提交:
>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XkrcfFdS-1616251474962)(media/24ad0054b9ca506374a755f3d61ea7ec.png)]
并不是你不想提交,而是工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办?

幸好,Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-90ATpzhR-1616251474962)(media/d6a3c13e6e83d89dbd64f7724f6baf4b.png)]

现在,用git status查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复bug。

首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支:

在这里插入图片描述

现在修复bug,需要把“Git is free software …”改为“Git is a free software …”,然后提交:
在这里插入图片描述

修复完成后,切换到master分支,并完成合并,最后删除issue-101分支:
在这里插入图片描述

太棒了,原计划两个小时的bug修复只花了5分钟!现在,是时候接着回到dev分支干活了!

>   [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vhTUkfCj-1616251474964)(media/7ad607a8cd9e973d7c3bfb79dd8966ba.png)]

工作区是干净的,刚才的工作现场存到哪去了?用git stash list命令看看:

在这里插入图片描述

工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法:

一是用git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除;

另一种方式是用git stash pop,恢复的同时把stash内容也删了:

在这里插入图片描述

再用git stash list查看,就看不到任何stash内容了。

你可以多次stash,恢复的时候,先用git stash list查看,然后恢复指定的stash,用命令:

$ git stash apply stash@{0}

5.6 特性分支

软件开发中,总有无穷无尽的新的功能要不断添加进来。

添加一个新功能时,你肯定不希望因为一些实验性质的代码,把主分支搞乱了,所以,每添加一个新功能,最好新建一个feature分支,在上面开发,完成后,合并,最后,删除该feature分支。

现在,你终于接到了一个新任务:开发代号为Vulcan的新功能,该功能计划用于下一代星际飞船。

于是准备开发:

在这里插入图片描述

5分钟后,开发完毕:

在这里插入图片描述

切回dev,准备合并:

$ git checkout dev

一切顺利的话,feature分支和bug分支是类似的,合并,然后删除。

但是!

就在此时,接到上级命令,因经费不足,新功能必须取消!

虽然白干了,但是这个包含机密资料的分支还是必须就地销毁:

在这里插入图片描述
现在我们强行删除:

在这里插入图片描述
可以看到删除成功了!

5.7 多人协作

当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin。

要查看远程库的信息,用git remote或者git remote -v:
在这里插入图片描述

上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。

  1. 推送分支:

    推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:

在这里插入图片描述

如果要推送其他分支,比如dev,就改成:
在这里插入图片描述
使用git branch -a可以查看本地和远程仓库的所有分支信息。
但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?

  • master分支是主分支,因此要时刻与远程同步;

  • dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;

  • bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;

  • feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。

  1. 抓取分支:

    多人协作时,大家都会往master和dev分支上推送各自的修改。

    现在,模拟一个你的小伙伴,可以在另一台电脑(注意要把SSH
    Key添加到GitHub)或者同一台电脑的另一个目录下克隆:

$ git clone git@github.com:flyunix/learngit.git

在这里插入图片描述

当你的小伙伴从远程库clone时,默认情况下,你的小伙伴只能看到本地的master分支。不信可以用git branch命令看看:

在这里插入图片描述
现在,你的小伙伴要在dev分支上开发,就必须创建远程origin的dev分支到本地,于是他用这个命令创建本地dev分支:

$ git checkout -b dev origin/dev

在这里插入图片描述

现在,他就可以在dev上继续修改,然后,时不时地把dev分支push到远程:
在这里插入图片描述

你的小伙伴已经向origin/dev分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送:

在这里插入图片描述
推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git已经提示我们,先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送:
在这里插入图片描述
git pull也失败了,解决办法有两个:一个是指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接:

$ git branch *--set-upstream-to=origin/dev dev*

Branch 'dev' **set** up **to** track remote branch 'dev'**from**'origin'.

在这里插入图片描述

另一个是git pull origin dev命令再pull一次。

再pull:
在这里插入图片描述

这回git pull成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的解决冲突完全一样。解决后,提交,再push:

在这里插入图片描述
push成功了!

因此,多人协作的工作模式通常是这样:

  1. 首先,可以试图用git push origin <branch-name>推送自己的修改;

  2. 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;

  3. 如果合并有冲突,则解决冲突,并在本地提交;

  4. 没有冲突或者解决掉冲突后,再用git push origin <branch-name>推送就能成功!

    如果git pull提示no tracking
    information,则说明本地分支和远程分支的链接关系没有创建,用命令git branch
    –set-upstream-to <branch-name> origin/<branch-name>。

    这就是多人协作的工作模式,一旦熟悉了,就非常简单。

5.8 删除远程分支

使用命令 git push origin --delete develops可以删除远程分支develops 再次使用命令 git branch -a 可以发现,远程分支develops已经被删除。

6. 标签管理

6.1 何为标签

发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。

Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。

Git有commit,为什么还要引入tag?

“请把上周一的那个版本打包发布,commit号是6a5819e…”

“一串乱七八糟的数字不好找!”

如果换一个办法:

“请把上周一的那个版本打包发布,版本号是v1.2”

“好的,按照tag v1.2查找commit就行!”

所以,tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。

6.2 创建标签

在Git中打标签非常简单,首先,切换到需要打标签的分支上:
在这里插入图片描述
然后,敲命令git tag <name>就可以打一个新标签,使用git tag查看所有的标签:
在这里插入图片描述

默认标签是打在最新提交的commit上的。有时候,如果忘了打标签,比如,现在已经是周五了,但应该在周一打的标签没有打,怎么办?

方法是找到历史提交的commit id,然后打上就可以了:

在这里插入图片描述
比方说要对add merge这次提交打标签,它对应的commit id是dfd5ef0,敲入命令:

$ git tag v0.9 dfd5ef0

再用命令git tag查看标签:

在这里插入图片描述

注意,标签不是按时间顺序列出,而是按字母排序的。可以用git show <tagname>查看标签信息:
在这里插入图片描述

可以看到,v0.9确实打在add merge这次提交上。

还可以创建带有说明的标签,用-a指定标签名,-m指定说明文字:

在这里插入图片描述

用命令git show <tagname>可以看到说明文字:

在这里插入图片描述
注意:标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。

6.3 管理标签

如果标签打错了,也可以删除:
在这里插入图片描述

因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。

如果要推送某个标签到远程,使用命令git push origin <tagname>:
在这里插入图片描述
或者,一次性推送全部尚未推送到远程的本地标签:
在这里插入图片描述
如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除。然后,从远程删除。删除命令也是push,但是格式如下:
在这里插入图片描述

7. Git自定义

7.1 文件忽略

有些时候,你必须把某些文件放到Git工作目录中,但又不能提交它们,比如保存了数据库密码的配置文件啦,等等,每次git status都会显示Untracked files …,有强迫症的童鞋心里肯定不爽。

好在Git考虑到了大家的感受,这个问题解决起来也很简单,在Git工作区的根目录下创建一个特殊的.gitignore文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件。

不需要从头写.gitignore文件,GitHub已经为我们准备了各种配置文件,只需要组合一下就可以使用了。所有配置文件可以直接在线浏览:https://github.com/github/gitignore

忽略文件的原则是:

  1. 忽略操作系统自动生成的文件,比如缩略图等;

  2. 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;

  3. 忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。

    举一个例子:

    下面是c语言项目的一般需要忽略的内容:

# Prerequisites
*.d

# Object files
*.o
*.ko
*.obj
*.elf

# Linker output
*.ilk
*.map
*.exp

# Precompiled Headers
*.gch
*.pch

# Libraries
*.lib
*.a
*.la
*.lo

# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib

# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex

# Debug files
*.dSYM/
*.su
*.idb
*.pdb

# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf

# My configurations:
... ... 

-在里面加上自己的配置信息,最后一步就把.gitignore也提交到Git,就完成了!当然检验.gitignore的标准是git
status命令是不是说working directory clean。

有些时候,你想添加一个文件到Git,但发现添加不了,原因是这个文件被.gitignore忽略了:

例如,我们添加一个test.o文件,由于.gitignore文件包含后缀为.o的文件,所以,正常情况下,test.o是添加不到版本库中。

在这里插入图片描述

如果你确实想添加该文件,可以用-f强制添加到Git:

在这里插入图片描述
或者你发现,可能是.gitignore写得有问题,需要找出来到底哪个规则写错了,可以用git check-ignore命令检查:
在这里插入图片描述
Git会告诉我们,.gitignore的第5行规则忽略了该文件,于是我们就可以知道应该修订哪个规则。

7.2 配置别名

git支持给命令起一个别名,目的是为了简化命令的记忆。比如,我们将git
status,使用别名st替代:

$ git config --global alias.st status

试一下,使用git st,如下:

在这里插入图片描述

当然还有别的命令可以简写,很多人都用co表示checkout,ci表示commit,br表示branch:

$ git config --global **alias**.co checkout

$ git config --global **alias**.ci commit

$ git config --global **alias**.br branch

以后提交就可以简写成:

$ git ci -m "bala bala bala..."

–global参数是全局参数,也就是这些命令在这台电脑的所有Git仓库下都有用。

撤销修改一节中,我们知道,命令git reset HEAD
file可以把暂存区的修改撤销掉(unstage),重新放回工作区。既然是一个unstage操作,就可以配置一个unstage别名:

$ git config --global alias.unstage ‘reset HEAD’

当你敲入命令:

$ git unstage test.txt

实际上Git执行的是:

$ git reset HEAD test.txt

7.3 配置文件

配置Git的时候,加上–global是针对当前用户起作用的,如果不加,那只针对当前的仓库起作用。

每个仓库本地的配置文件的位置都放在本仓库的.git/config文件中:

在这里插入图片描述

别名就在[alias]后面,要删除别名,直接把对应的行删掉即可。

而当前用户的Git配置文件放在用户主目录下的一个隐藏文件.gitconfig中(/c/Users/Administrator):

在这里插入图片描述
配置别名也可以直接修改这个文件,如果改错了,可以删掉文件重新通过命令配置。

子模块管理

在平时的项目开发过程中,你可能遇到过这样的需求:当前的项目想要引入另一个库,这个库可能是一个第三方库或者是一个你自己开发的框架库,而你想要独立的维护这两个项目,即,保持当前项目和导入库各自git库之间的独立性。这时,你可能需要用到git子模块相关的知识。

Git子模块允许你在git项目中以子目录的形式引入另一个或几个git库,而各个git库之间完全是独立的,这就为各个git库的开发和维护带来的很大的灵活性和便利性。下面就具体介绍一下子模块的具体使用方式。具体演示时,会使用到Gitee代码托管平台,用于远端代码库的存放。

8.1 创建子模块

子模块是以子目录的形式存在于现有git库中的,其创建方式十分的简单,比如,当前的项目为gitLearn库,可以通过如下命令将其克隆到本地:

git clone git@gitee.com:flynix/git-learn.git

现在在当前代码库中添加gitSubModule子模块,命令如下:

git submodule add git@gitee.com:flynix/git-sub-module.git

在这里插入图片描述
这是可以看到当前代码库中增加了一个git-sub-module的子目录,其里面就是克隆到本地的git-sub-module代码库。通过git status命令可以到当前工作区的修改。
在这里插入图片描述
其中,.gtimodules文件用于保存当前子模块的信息,如果有多个子模块,这里相对应的就会存在多条记录。
在这里插入图片描述
在当前项目中,Git如何看待子模块中的内容呢?这可以通过git diff --cached命令查看。

在这里插入图片描述
可以看到Git将目录git-sub-module当做一个整体子模块对待,当你不在那个目录中时,Git并不会跟踪它的内容, 而是将它看作子模块仓库中的某个具体的提交。使用git diff --cached --submodule可以看到更好的子模块输出。
在这里插入图片描述
好了,提交当前的版本库修改信息。
在这里插入图片描述
注意 git-sub-module 记录的 160000 模式。 这是 Git中的一种特殊模式,它本质上意味着你是将一次提交记作一项目录记录的,而非将它记录成一个子目录或者一个文件。

最后,推送本地修改到远程代码库。
在这里插入图片描述

8.2 克隆子模块

现在git-learn代码库是一个包含子模块的代码库了,那如何克隆包含子模块的代码呢?

在这里插入图片描述
我们通过git clone git@gitee.com:flynix/git-learn.git克隆git-learn后,只能看到一个空的git-sub-module目录,后续需要使用下面两个命令拉取子模块的内容:

 git submodule init;

git submodule update;

在这里插入图片描述
git submodule init 用来初始化本地配置文件。

git submodule update 则从该项目中抓取所有数据并检出父项目中列出的合适的提交。

现在 git-sub-module子目录是处在和之前提交时相同的状态了。

可以在git clone时添加–recurse-submodules参数,它就会自动初始化并更新仓库中的每一个子模块,包括可能存在的嵌套子模块。

在这里插入图片描述

8.3 维护子模块

8.3.1拉取远端修改

如果子模块的远端有了新的提交,如何在本地代码库更新远端的提交呢?

比如,事先 git-sub-module中增加一个helloworld新文件,并提交到远端。

在这里插入图片描述

然后,进入git-sub-module中,通过git fetch和git merge合并远端的提交。

在这里插入图片描述

返回,git-learn库主目录,通过git diff --submodule查看子模块的更新信息。
在这里插入图片描述
可以将 diff.submodule 设置为 “log” 来将其作为默认行为。
在这里插入图片描述
如果不想在子目录下更新远端的子模块新提交,可以使用如下的命令git submodule update --remote,Git 将会进入子模块然后抓取并更新。

在这里插入图片描述

如果在此时提交,那么你会将子模块锁定为其他人更新时的新代码。即,如果使用添加了–recurse-submodules参数git clone命令拉取git-learn库,将会拉取到最新的子模块代码信息。

此命令默认会假定你想要更新并检出子模块仓库的 master 分支。不过你也可以设置为想要的其他分支。 例如,你想要 git-sub-module子模块跟踪仓库的 “devs” 分支,那么既可以在 .gitmodules 文件中设置(这样其他人也可以跟踪它),也可以只在本地的 .git/config 文件中设置。让我们在 .gitmodules 文件中设置它:

git config -f .gitmodules submodule.git-sub-module.branch devs

这是使用git status显示工作区的状态。

在这里插入图片描述

如果你设置了git config status.submodulesummary 1选项,可以看到更为详细的子模块信息。

在这里插入图片描述

运行git diff可以看到当前工作区的修改信息,甚至看到子模块的提交记录信息。

在这里插入图片描述

当运行 git submodule update --remote 时,Git 默认会尝试更新所有子模块,所以如果有很多子模块的话,你可以传递想要更新的子模块的名字。

8.3.2 项目成员协作

其他项目程序本地已经存在了git-learn项目,他可以使用git pull拉取远程最新的提交,如下:

首先,在git-sub-module中添加一个新文件newfile,并推送到远程代码库。目前,存在两个git-learn代码库:git-learn和git-learn-vs,在git-learn拉取最新的git-sub-module的提交信息。

在这里插入图片描述

并将当前的提交推送到远程服务器。
在这里插入图片描述

在git-learn-vs中使用git pull拉取最新的提交,执行git status可以看到子模块信息已经修改,但是并未更新。

在这里插入图片描述

默认情况下,git pull命令会递归归地抓取子模块的更改,如上面第一个命令的输出显示。然而,它不会更新子模块。这点可通过git status命令看到,它会显示子模块“已此外,左边的尖括号(<)指出了新的提交,表示这些提交已在git-learn中记录,但尚未在本地的git-sub-module中检出。为了完成更新,你需要运行git submodule update:
在这里插入图片描述

8.3.3 在子模块上工作

在子模块上工作与常规的git库工作方式大同小异。这里需要注意的是,默认情况下,当我们运行git submodule
update从子模块仓库中抓取修改时,Git将会获得这些扩展并更新子目录中的文件,但是替换子仓库留在一个替换“游离的HEAD”的状态。这如果没有工作分区跟踪更改,也就意味着甚至您将更改提交到了子模块,这些更改也很可能会在下次运行
git submodule update时丢失。要在子模块中跟踪这些修改,还需要一些额外的步骤。为了能够正常的对子模块完成修改工作,需要完成下面两步工作:

  1. 在子模块中检出相应的工作分支。

  2. 执行git submodule update –remote
    从远端获取最新的提交,并合并到本地分支中。
    在这里插入图片描述
    如果在未提交子模块中的修改,就开始同步上游的子模块提交,这时会提示,本地修改将会被覆盖,同步更新终止。
    在这里插入图片描述
    如果本地子模块的修改和上游子模块之前发生冲突,可以通过正常冲突解决方式进行冲突解决。

Q&A

1. fatal: in unpopulated submodule XXX

比如,出现下面的提示:

fatal: in unpopulated submodule hello.c

因为hello.c是从另一个第三方库克隆得来的,所以会出现上面的错误提示。

附录

1. 参考链接:

Pro Git中文

廖雪峰Git教程

2. windows记事本相关的坑

千万不要使用Windows自带的记事本编辑任何文本文件。原因是Microsoft开发记事本的团队使用了一个非常弱智的行为来保存UTF-8编码的文件,他们自作聪明地在每个文件开头添加了0xefbbbf(十六进制)的字符,你会遇到很多不可思议的问题,比如,网页第一行可能会显示一个“?”,明明正确的程序一编译就报语法错误,等等,都是由记事本的弱智行为带来的。建议你下载Notepad++代替记事本,不但功能强大,而且免费!记得把Notepad++的默认编码设置为UTF-8 without BOM即可:

在这里插入图片描述

3. Git warning:LF will be replaced by CRLF in readme.txt的原因与解决方案

首先问题出在不同操作系统所使用的换行符是不一样的,下面罗列一下三大主流操作系统的换行符:

Uinx/Linux采用换行符LF表示下一行(LF:LineFeed,中文意思是换行);

Dos和Windows采用回车+换行CRLF表示下一行(CRLF:CarriageReturn LineFeed,中文意思是回车换行);

Mac OS采用回车CR表示下一行(CR:CarriageReturn,中文意思是回车)。

在Git中,可以通过以下命令来显示当前你的Git中采取哪种对待换行符的方式

$ git config core.autocrlf

此命令会有三个输出,“true”,“false”或者“input”为true时,Git会将你add的所有文件视为文本问价你,将结尾的CRLF转换为LF,而checkout时会再将文件的LF格式转为CRLF格式。

为false时,line endings不做任何改变,文本文件保持其原来的样子。

为input时,add时Git会把CRLF转换为LF,而check时仍旧为LF,所以Windows操作系统不建议设置此值。

解决办法:

将core.autocrlf设为false即可解决这个问题,不过如果你和你的伙伴只工作于Windows平台或者Linux平台,那么没问题,不过如果是存在跨平台的现象的话,还是需要考虑一下。

但当 core autocrlf为true时,还有一个需要慎重的地方,当你上传一个二进制文件,Git可能会将二进制文件误以为是文本文件,从而也会修改你的二进制文件,从而产生隐患。

PS:
附上修改autocrlf的命令,以改为true为例:

$ git config --global core.autocrlf true
\#true的位置放你想使autocrlf成为的结果,true,false或者input。
  • 8
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值