分布式版本控制系统Git的安装与使用

作业要求

1.(本次作业要求来自:https://edu.cnblogs.com/campus/gzcc/GZCC-16SE1/homework/2103

2.  我的Github远程仓库地址: https://github.com/llgeill/llg-centos-git--test

3.  我的Github远程仓库地址截图

作业内容

1.Git的由来

很多人都知道,Linus在1991年创建了开源的Linux,从此,Linux系统不断发展,已经成为最大的服务器系统软件了。

Linus虽然创建了Linux,但Linux的壮大是靠全世界热心的志愿者参与的,这么多人在世界各地为Linux编写代码,那Linux的代码是如何管理的呢?

事实是,在2002年以前,世界各地的志愿者把源代码文件通过diff的方式发给Linus,然后由Linus本人通过手工方式合并代码!

你也许会想,为什么Linus不把Linux代码放到版本控制系统里呢?不是有CVS、SVN这些免费的版本控制系统吗?因为Linus坚定地反对CVS和SVN,这些集中式的版本控制系统不但速度慢,而且必须联网才能使用。有一些商用的版本控制系统,虽然比CVS、SVN好用,但那是付费的,和Linux的开源精神不符。

不过,到了2002年,Linux系统已经发展了十年了,代码库之大让Linus很难继续通过手工方式管理了,社区的弟兄们也对这种方式表达了强烈不满,于是Linus选择了一个商业的版本控制系统BitKeeper,BitKeeper的东家BitMover公司出于人道主义精神,授权Linux社区免费使用这个版本控制系统。

安定团结的大好局面在2005年就被打破了,原因是Linux社区牛人聚集,不免沾染了一些梁山好汉的江湖习气。开发Samba的Andrew试图破解BitKeeper的协议(这么干的其实也不只他一个),被BitMover公司发现了(监控工作做得不错!),于是BitMover公司怒了,要收回Linux社区的免费使用权。

Linus可以向BitMover公司道个歉,保证以后严格管教弟兄们,嗯,这是不可能的。实际情况是这样的:

Linus花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!一个月之内,Linux系统的源码已经由Git管理了!牛是怎么定义的呢?大家可以体会一下。

Git迅速成为最流行的分布式版本控制系统,尤其是2008年,GitHub网站上线了,它为开源项目免费提供Git存储,无数开源项目开始迁移至GitHub,包括jQuery,PHP,Ruby等等。

历史就是这么偶然,如果不是当年BitMover公司威胁Linux社区,可能现在我们就没有免费而超级好用的Git了。

2.什么是Git

  • 从定义上来说Git就是一个分布式版本控制系统
  • 从作用上来说Git就是管理一些文本或者代码的版本更新,例如内容的改动
  • 从管理的对象来说,所有的版本控制都只能针对文本内容,像word这样的二进制内容,所谓版本,就是文件快照,不能比较差异,只能算带备份的网盘

3.为什么git叫做分布式版本控制系统

如果要想了解分布式是什么意思,那么我们得先去了解它的对立面集中式

集中式版本控制系统

  • 工具:CVS及SVN都是集中式的版本控制系统
  • 内容:集中式版本控制系统集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。
  • 缺点:集中式版本控制系统最大的毛病就是必须联网才能工作,如果在局域网内还好,带宽够大,速度够快,可如果在互联网上,遇到网速慢的话,可能提交一个10M的文件就需要5分钟,这还不得把人给憋死啊。

central-repo

 

分布式版本控制系统

工具:git

内容:布式版本控制系统没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。既然每个人电脑上都有一个完整的版本库,那多个人如何协作呢?比方说你在自己电脑上改了文件A,你的同事也在他的电脑上改了文件A,这时,你们俩之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。

优点:和集中式版本控制系统相比,分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了。而集中式版本控制系统的中央服务器要是出了问题,所有人都没法干活了。在实际使用分布式版本控制系统的时候,其实很少在两人之间的电脑上推送版本库的修改,因为可能你们俩不在一个局域网内,两台电脑互相访问不了,也可能今天你的同事病了,他的电脑压根没有开机。因此,分布式版本控制系统通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。当然,Git的优势不单是不必联网这么简单,后面我们还会看到Git极其强大的分支管理,把SVN等远远抛在了后面(svn其实也有分支功能,不过是在服务器上的分支)

distributed-repo

 

集中式与分布式的差异性

  1. 分布式在每一个用户上都有一个版本控制而集中式没有
  2. 由于每一个用户都有自己的版本控制工具,所以服务器压力几乎没有,而集中式版本工具的压力全在服务器上,所以在协作人数上面分布式版本工具更胜一筹

4.开始安装Git

Git安装

可以直接从官网下载相应操作系统的Git然后进行安装,当然也可以使用命令行的方式

  • 例如在使用Centos操作系统的时候,可以使用如下命令快速安装
  • yum install -y git
  • 或着不想使用命令行方式就直接去官网下载压缩包
https://git-scm.com/

Git bash配置

用户名和邮箱地址的作用

  1. 用户名和邮箱地址是本地git客户端的一个变量,不随git库而改变。
  2. 每次commit都会用用户名和邮箱纪录。
  3. github的contributions统计就是按邮箱来统计的。

修改用户名和邮箱地址

  • $ git config --global user.name "username"
  • $ git config --global user.email  "email"

查看用户名和邮箱地址

  • $ git config user.name
  • $ git config user.email

5.开始使用Git(版本库)

以下所有操作都在centos上操作完成

什么是版本库

版本库又名仓库,英文名repository,你可以简单理解成一个文件夹,这个文件夹里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。所以,创建一个版本库非常简单。

开始创建版本库

首先,选择一个合适的地方,创建一个空目录

#创建一个文件件
[llg@localhost 桌面]$ mkdir llg-test-git
#进入文件夹目录
[llg@localhost 桌面]$ cd llg-test-git/
#显示当前文件夹路径
[llg@localhost llg-test-git]$ pwd

接着,最重要的一步 通过git init 的命令将当前文件夹变成一个版本库

[llg@localhost llg-test-git]$ git init
初始化空的 Git 版本库于 /home/llg/桌面/llg-test-git/.git/
[llg@localhost llg-test-git]$

最后,通过ls -al 命令,发现多了个git文件夹。这个目录是Git来跟踪管理版本库的,没事千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了。

[llg@localhost llg-test-git]$ ls -al
总用量 4 
drwxrwsr-x 3 llg llg 18 9月 14 19:48 . 
drwsrwsrwt. 13 llg llg 4096 9月 14 19:44 ..
drwxrwsr-x 7 llg llg 119 9月 14 19:48 .git
[llg@localhost llg-test-git]$

 

开始添加文件到版本库

首先需要注意的是版本管理的受众范围

  • 首先这里再明确一下,所有的版本控制系统,其实只能跟踪文本文件的改动,比如TXT文件,网页,所有的程序代码等等,Git也不例外。版本控制系统可以告诉你每次的改动,比如在第5行加了一个单词“Linux”,在第8行删了一个单词“Windows”。而图片、视频这些二进制文件,虽然也能由版本控制系统管理,但没法跟踪文件的变化,只能把二进制文件每次改动串起来,也就是只知道图片从100KB改成了120KB,但到底改了啥,版本控制系统不知道,也没法知道。
  • 不幸的是,Microsoft的Word格式是二进制格式,因此,版本控制系统是没法跟踪Word文件的改动的,前面我们举的例子只是为了演示,如果要真正使用版本控制系统,就要以纯文本方式编写文件。
  • 因为文本是有编码的,比如中文有常用的GBK编码,日文有Shift_JIS编码,如果没有历史遗留问题,强烈建议使用标准的UTF-8编码,所有语言使用同一种编码,既没有冲突,又被所有平台所支持。
  • 使用Windows的童鞋要特别注意:千万不要使用Windows自带的记事本编辑任何文本文件。原因是Microsoft开发记事本的团队使用了一个非常弱智的行为来保存UTF-8编码的文件,他们自作聪明地在每个文件开头添加了0xefbbbf(十六进制)的字符,你会遇到很多不可思议的问题,比如,网页第一行可能会显示一个“?”,明明正确的程序一编译就报语法错误,等等,都是由记事本的弱智行为带来的。建议你下载Notepad++代替记事本,不但功能强大,而且免费!记得把Notepad++的默认编码设置为UTF-8 without BOM即可:

首先,使用vi创建一个文件并且添加一些内容。确保此文件必须在这个版本仓库里面也就是我们恶毒llg-test-git文件夹,不然放在其他地方是不能被版本控制的。

[llg@localhost llg-test-git]$ vi llg.txt
[llg@localhost llg-test-git]$

接着,将文件显示的通过git add命令添加到版本库里面,不过在这之前我们使用git status 来查看一下还没使用git add命令时候的状态和使用git add之后的状态,比较一下不同。通过比较,我们发现了当使用git add 命令的时候其实是建立起了文件跟踪的功能,之后使用git status 就可以看到有一个新文件

[llg@localhost llg-test-git]$ git status
# 位于分支 master
# 
# 初始提交 
# 
# 未跟踪的文件: 
# (使用 "git add <file>..." 以包含要提交的内容) 
# 
# llg.txt 
提交为空,但是存在尚未跟踪的文件(使用 "git add" 建立跟踪) 
[llg@localhost llg-test-git]$
[llg@localhost llg-test-git]$ git add llg.txt
[llg@localhost llg-test-git]$ git status
# 位于分支 master 
# 
# 初始提交 
# 
# 要提交的变更: 
# (使用 "git rm --cached <file>..." 撤出暂存区) 
# 
# 新文件: llg.txt 
# 
[llg@localhost llg-test-git]$

 

最后,使用git commit -m告诉Git,把文件提交到仓库。-m是提交的一些说明信息,必须要写,可以方便自己和他人了解这次提交了什么东西。

 

 

[llg@localhost llg-test-git]$ git commit -m "git练习测试" 
[master(根提交) abde635] git练习测试 
1 file changed, 1 insertion(+) 
create mode 100644 llg.txt 
[llg@localhost llg-test-git]$

 

疑问:为什么Git添加文件需要addcommit一共两步呢?

因为commit可以一次提交很多文件,所以你可以多次add不同的文件,比如下图,可以跟踪多个文件然后全部一次性提交到版本库里面.

[llg@localhost llg-test-git]$ vi one.txt 
[llg@localhost llg-test-git]$ vi two.txt 
[llg@localhost llg-test-git]$ vi three.txt 
[llg@localhost llg-test-git]$ git add one.txt 
[llg@localhost llg-test-git]$ git add two.txt 
[llg@localhost llg-test-git]$ git add three.txt 
[llg@localhost llg-test-git]$ git commit -m "这次测试一次提交三个文件" 
[master 44449dc] 这次测试一次提交三个文件 
3 files changed, 4 insertions(+) 
create mode 100644 one.txt 
create mode 100644 three.txt 
create mode 100644 two.txt 
[llg@localhost llg-test-git]$
[llg@localhost llg-test-git]$ git status 
# 位于分支 master 
无文件要提交,干净的工作区 
[llg@localhost llg-test-git]$

 

6.Git版本回退

在学习版本回退之前我们线来了解下两个常用的命令git status和git diff

git status

刚刚在创建版本库的时候我们已经使用了,用来查看git的状态,一般可以拿来查看我们做的某些操作之后的状态,例如修改文件,添加文件等等

例如我们修改一个文件然后使用git status查看,发现了提示llg.txt是修改过的文件

 


[llg@localhost llg-test-git]$ vi llg.txt 
[llg@localhost llg-test-git]$ git status 
# 位于分支 master 
# 尚未暂存以备提交的变更:
# (使用 "git add <file>..." 更新要提交的内容)
# (使用 "git checkout -- <file>..." 丢弃工作区的改动) 
# 
# 修改: llg.txt 
# 
修改尚未加入提交(使用 "git add" 和/或 "git commit -a") 
[llg@localhost llg-test-git]$

 

 

git sdiff

由于文件修改过后,我们有时候可能想知道具体修改了什么内容,位置在那里,区别是什么等等。,所以我们需要git sdiff命令来获得这个修改前后的对照信息

[llg@localhost llg-test-git]$ git diff 
diff --git a/llg.txt b/llg.txt 
index f77faef..2f3d837 100644 
--- a/llg.txt 
+++ b/llg.txt 
@@ -1 +1 @@ 
-my name is liliguang 
+My name is liliguang.I created three files a moment ago. 
[llg@localhost llg-test-git]$

 

之后继续测试,修改其他两个文件内容
[llg@localhost llg-test-git]$ vi one.txt 
[llg@localhost llg-test-git]$ vi two.txt 
[llg@localhost llg-test-git]$ git diff
diff --git a/llg.txt b/llg.txt
index f77faef..2f3d837 100644
--- a/llg.txt
+++ b/llg.txt
@@ -1 +1 @@
-my name is liliguang
+My name is liliguang.I created three files a moment ago.
diff --git a/one.txt b/one.txt
index 25ab921..34144e1 100644
--- a/one.txt
+++ b/one.txt
@@ -1 +1,2 @@
this is one
+llg
diff --git a/two.txt b/two.txt
index 42b3fbc..0f62931 100644
--- a/two.txt
+++ b/two.txt
@@ -1,2 +1,2 @@
this is two
-
+llg
[llg@localhost llg-test-git]$

 

[llg@localhost llg-test-git]$ vi two.txt
[llg@localhost llg-test-git]$ git diff
diff --git a/llg.txt b/llg.txt
index f77faef..2f3d837 100644
--- a/llg.txt
+++ b/llg.txt
@@ -1 +1 @@
-my name is liliguang
+My name is liliguang.I created three files a moment ago.
diff --git a/one.txt b/one.txt
index 25ab921..34144e1 100644
--- a/one.txt
+++ b/one.txt
@@ -1 +1,2 @@
this is one
+llg
diff --git a/two.txt b/two.txt
index 42b3fbc..e730358 100644
--- a/two.txt
+++ b/two.txt
@@ -1,2 +1,6 @@
this is two
-
+llg
+sss
+aaa
+bbb
+wef
[llg@localhost llg-test-git]$

 

 

开始使用版本回退的前提

版本回退的意思就是回退到某一次commit那里,这优点类似与打通关游戏,你可以在任意通过的关卡中来往穿梭

git log

首先,我们需要使用git log 命令来找出我们之前的commit信息。从下图可以看到提交顺序是按照最新日期排序的,最新的在最顶部,commit 后面的就是commit ID 了,后面的版本回退都靠它

 

[llg@localhost llg-test-git]$ git log
commit 3956cc36e1017c14e79d9de0944bb9baa6d2ff51
Author: llg <903857227@qq.com>
Date: Fri Sep 14 20:35:26 2018 +0800

测试两个文件提交留一个文件不提交

commit 44449dc5261b8a66bac14ce302519f26895d0163 
Author: llg <903857227@qq.com>
Date: Fri Sep 14 20:11:22 2018 +0800
 
 
这次测试一次提交三个文件
 
 
commit abde635c93434ac4172caa4e0c6383e9dfc3d522
Author: llg <903857227@qq.com>
Date: Fri Sep 14 20:07:31 2018 +0800
 
 
git练习测试
 
[llg@localhost llg-test-git]$

 

如果嫌输出信息太多,看得眼花缭乱的,可以试试加上--pretty=oneline参数

 

 

[llg@localhost llg-test-git]$ git log --pretty=oneline
3956cc36e1017c14e79d9de0944bb9baa6d2ff51 测试两个文件提交留一个文件不提交 
44449dc5261b8a66bac14ce302519f26895d0163 这次测试一次提交三个文件 
abde635c93434ac4172caa4e0c6383e9dfc3d522 git练习测试 
[llg@localhost llg-test-git]$

 

 

开始使用版本回退

首先,Git必须知道当前版本是哪个版本,在Git中,用HEAD表示当前版本,也就是最新的提交3956cc...(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100

git reset

我们要把当前版本<测试两个文件提交留一个文件不提交>回退到上一个版本<这次测试一次提交三个文件>,就可以使用git reset命令:

从下图我们可以分析出,其实HEAD代表的是头指针,毕竟这个git使用c语言写的,我们对C应该也是不陌生的,所以这是一个双向链表,可以向前也可以向后,只要我们知道commit ID。

 

[llg@localhost llg-test-git]$ git reset --hard HEAD
HEAD 现在位于 3956cc3 测试两个文件提交留一个文件不提交
[llg@localhost llg-test-git]$ git reset --hard HEAD^
HEAD 现在位于 44449dc 这次测试一次提交三个文件
[llg@localhost llg-test-git]$ git reset --hard HEAD^
HEAD 现在位于 abde635 git练习测试
[llg@localhost llg-test-git]$
 

 

git-headgit-head-move

 

当我们跳的比较远的时候,也可以直接指定commit ID 来跳转 

 

[llg@localhost llg-test-git]$ git reset --hard 3956cc3
HEAD 现在位于 3956cc3 测试两个文件提交留一个文件不提交
[llg@localhost llg-test-git]$

 

当我们想要回到之前最新的版本的时候,我们会想到用git log,但是很不幸我们发现之前的版本信息都没有了。所以我们还有一个命令用来找回之前的commit ID ,就是git reflog命令了。

 

[llg@localhost llg-test-git]$ git log
commit abde635c93434ac4172caa4e0c6383e9dfc3d522
Author: llg <903857227@qq.com>
Date: Fri Sep 14 20:07:31 2018 +0800
git练习测试
[llg@localhost llg-test-git]$

 

 

 

 

git reflog

可以拿到所有的操作记录

从下面我们可以找到带有commit字样的就是我们之前创建的版本,但是只提供了ID的七为数,但是没关系,git会自动识别出来

 

 
[llg@localhost llg-test-git]$ git reflog
abde635 HEAD@{0}: reset: moving to HEAD^
44449dc HEAD@{1}: reset: moving to HEAD^
3956cc3 HEAD@{2}: commit: 测试两个文件提交留一个文件不提交
44449dc HEAD@{3}: commit: 这次测试一次提交三个文件
abde635 HEAD@{4}: commit (initial): git练习测试
[llg@localhost llg-test-git]$ git reset --hard 3956cc3
HEAD 现在位于 3956cc3 测试两个文件提交留一个文件不提交
[llg@localhost llg-test-git]$

 

 

 

 

 

7.git的工作区和暂存区

首先是官图解释,从下图可以看出如果我们想直接提交文件到master分支上,那么直接使用 git commit -a即可

接着再看看其余大佬绘制的图

版本库(Repository)

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

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

git-repo

 

假如我们在commit之后没有任何操作,那么我们暂存区就是空的

git-stage-after-commit

当我们添加了文件或者修改了文件,并且使用了add命令那么暂存区stage就又有了文件

git-stage

 

当我们使用commit提交之后,那么早存区又变成了空

git-stage-after-commit

 

关于git diff 与暂存区的关系

  • git diff 比较的是工作区文件与暂存区文件的区别(上次git add 的内容)
  • git diff --cached 比较的是暂存区的文件与仓库分支里(上次git commit 后的内容)的区别

8.Git如何撤回修改的操作

场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file

首先修改工作区的文件,不执行add操作

检查git状态,发现提示使用git checkout -- file 命令撤回操作 ,执行之后发现已经成功撤回

 
[llg@localhost llg-test-git]$ git status
# 位于分支 master
# 尚未暂存以备提交的变更:
# (使用 "git add <file>..." 更新要提交的内容)
# (使用 "git checkout -- <file>..." 丢弃工作区的改动)
#
# 修改: one.txt
#
修改尚未加入提交(使用 "git add" 和/或 "git commit -a")
[llg@localhost llg-test-git]$
[llg@localhost llg-test-git]$ git checkout -- one.txt
[llg@localhost llg-test-git]$

 

 

 

 

 

 

 

场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD <file>,就回到了场景1,第二步按场景1操作。

首先修改文件并且通过add放到暂存区 

 

[llg@localhost llg-test-git]$ git status
# 位于分支 master
# 要提交的变更:
# (使用 "git reset HEAD <file>..." 撤出暂存区)
#
# 修改: one.txt
#
[llg@localhost llg-test-git]$

 

 

 

 

根据提示使用git reset HEAD <file> 撤出暂存区,执行成功 

 

 
[llg@localhost llg-test-git]$ git status
# 位于分支 master
# 要提交的变更:
# (使用 "git reset HEAD <file>..." 撤出暂存区)
#
# 修改: one.txt
#
[llg@localhost llg-test-git]$ git reset HEAD one.txt
重置后撤出暂存区的变更:
M one.txt
[llg@localhost llg-test-git]$ git status
# 位于分支 master
# 尚未暂存以备提交的变更:
# (使用 "git add <file>..." 更新要提交的内容)
# (使用 "git checkout -- <file>..." 丢弃工作区的改动)
#
# 修改: one.txt
#
修改尚未加入提交(使用 "git add" 和/或 "git commit -a")
[llg@localhost llg-test-git]$

 

场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。

9.Git如何删除文件

此处的删除文件是指当一个文件提交到版本库后,也就是master分支。如果在工作区删除这个文件,那么将会使工作区文件与版本库文件不对应,在这里需要分清两种使用情况。

工作区删除了文件并且确实要从版本库中也删除该文件

那么就应该使用git rm 命令

 
[llg@localhost llg-test-git]$ rm delete.txt
[llg@localhost llg-test-git]$ git rm -- delete.txt
rm 'delete.txt'
[llg@localhost llg-test-git]$ git status
# 位于分支 master
# 要提交的变更:
# (使用 "git reset HEAD <file>..." 撤出暂存区)
#
# 删除: delete.txt
#
[llg@localhost llg-test-git]$

 

 

 

当然我们通过git status 命令可以看出这一操作只是保留在了暂存区,所以我们还需要commit命令提交这一更改,其实可以把这一命令想象成修改的命令

 

[llg@localhost llg-test-git]$ git commit -m "正式从版本库中删除该文件"
[master ad66495] 正式从版本库中删除该文件
1 file changed, 1 deletion(-)
delete mode 100644 delete.txt
[llg@localhost llg-test-git]$ git status
# 位于分支 master
无文件要提交,干净的工作区
[llg@localhost llg-test-git]$

 

 

工作区误删了某些文件,需要从版本库中重新弄回来

根据git status 提示的方法,我们可以从版本库中checkout中某些文件

[llg@localhost llg-test-git]$ git checkout -- delete.txt

另外一个一个简单的方法就是重新把版本库更新回来,但是这个前提是你得保证只有这么一个删除文件,如果有其他文件修改了并且未放到暂存区,那么就非常可怕了。

 

[llg@localhost llg-test-git]$ git reset --hard HEAD^
HEAD 现在位于 eda17f9 这是一个将要被删除的文件
[llg@localhost llg-test-git]$ ls -al 
总用量 24 
drwxrwsr-x 3 llg llg 98 9月 15 19:56 .
drwsrwsrwt. 13 llg llg 4096 9月 14 19:44 .. 
-rw-rw-r-- 1 llg llg 34 9月 15 19:56 delete.txt 
drwxrwsr-x 8 llg llg 183 9月 15 19:56 .git 
-rw-rw-r-- 1 llg llg 57 9月 14 21:06 llg.txt 
-rw-rw-r-- 1 llg llg 16 9月 14 22:05 one.txt 
-rw-rw-r-- 1 llg llg 14 9月 14 21:06 three.txt
-rw-rw-r-- 1 llg llg 13 9月 14 21:06 two.txt 
[llg@localhost llg-test-git]$

 

10.初识远程仓库

既然git的其中一个目的就是为了协作开发,那么远程仓库就是为了这一个目的的。通过将仓库放在一个远程的服务器上,让所有人都可以访问这个仓库,可以一起更新和提交。

实际情况往往是这样,找一台电脑充当服务器的角色,每天24小时开机,其他每个人都从这个“服务器”仓库克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。

完全可以自己搭建一台运行Git的服务器,不过现阶段,为了学Git先搭个服务器绝对是小题大作。好在这个世界上有个叫GitHub的神奇的网站,从名字就可以看出,这个网站就是提供Git仓库托管服务的,所以,只要注册一个GitHub账号,就可以免费获得Git远程仓库。

在继续阅读后续内容前,请自行注册GitHub账号。由于你的本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以,需要一点设置:

第1步:创建SSH Key。在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsaid_rsa.pub这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:

[llg@localhost ~]$ ssh-keygen -t rsa -C "903857227@qq.com"

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

第2步:登陆GitHub,打开“Account settings”,“SSH Keys”页面:

然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub文件的内容:

[llg@localhost ~]$ cd .ssh/

 

[llg@localhost .ssh]$ ls -al 
总用量 16 
drwsrwsrwt. 2 llg llg 57 9月 15 20:28 .
drwsrwsrwt. 39 llg llg 4096 9月 15 19:59 ..
-rw------- 1 llg llg 1679 9月 15 20:29 id_rsa 
-rw-r--r-- 1 llg llg 398 9月 15 20:29 id_rsa.pub 
-rw-r--r-- 1 llg llg 175 4月 27 20:05 known_hosts 
[llg@localhost .ssh]$ vi id_rsa.pub

 

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

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

最后友情提示,在GitHub上免费托管的Git仓库,任何人都可以看到喔(但只有你自己才能改)。所以,不要把敏感信息放进去。

如果你不想让别人看到Git库,有两个办法,一个是交点保护费,让GitHub把公开的仓库变成私有的,这样别人就看不见了(不可读更不可写)。另一个办法是自己动手,搭一个Git服务器,因为是你自己的Git服务器,所以别人也是看不见的。这个方法我们后面会讲到的,相当简单,公司内部开发必备。

确保你拥有一个GitHub账号后,我们就即将开始远程仓库的学习。

11.从github上添加一个远程仓库

首先我们github上登陆自己帐号后创建一个仓库

接着我们可以看到创建完仓库后给我们的提示

从本地的已有仓库直接推送到远程仓库上

根据上边提示输入命令

 

[llg@localhost llg-test-git]$ git remote add origin https://github.com/llgeill/llg-centos-git--test.git
[llg@localhost llg-test-git]$ git push -u origin master 
Username for 'https://github.com': 903857227@qq.com
Password for 'https://903857227@qq.com@github.com': 
Counting objects: 12, done.
Delta compression using up to 4 threads. 
Compressing objects: 100% (6/6), done.
Writing objects: 100% (12/12), 1.01 KiB | 0 bytes/s, done. 
Total 12 (delta 0), reused 0 (delta 0)
remote:
remote: Create a pull request for 'master' on GitHub by visiting:
remote: https://github.com/llgeill/llg-centos-git--test/pull/new/master
remote:
To https://github.com/llgeill/llg-centos-git--test.git
* [new branch] master -> master
分支 master 设置为跟踪来自 origin 的远程分支 master。

 

 

发现文件已经推送完毕 

 

Git远程仓库的优点 

  • 要关联一个远程库,使用命令git remote add origin git@server-name:path/repo-name.git
  • 关联后,使用命令git push -u origin master第一次推送master分支的所有内容;
  • 此后,每次本地提交后,只要有必要,就可以使用命令git push origin master推送最新修改;
  • 分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,而SVN在没有联网的时候是拒绝干活的!当有网络的时候,再把本地提交推送一下就完成了同步,真是太方便了!

12.从远程仓库克隆一个已有仓库

首先我们从github上创建一个远程仓库

首先在github上找到可以克隆的地址

下一步是用命令git clone克隆一个本地库,我们可以发现项目已经克隆下来

 

 
[llg@localhost 桌面]$ git clone git@github.com:llgeill/llg-centos-git-test-1.git
正克隆到 'llg-centos-git-test-1'...
The authenticity of host 'github.com (192.30.253.112)' can't be established.
RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
RSA key fingerprint is MD5:16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'github.com,192.30.253.112' (RSA) to the list of known hosts. 
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
接收对象中: 100% (3/3), done.
[llg@localhost 桌面]$ ls -al
总用量 40
drwsrwsrwt. 14 llg llg 4096 9月 15 21:10 .
drwsrwsrwt. 39 llg llg 4096 9月 15 20:39 ..
drwxr-xr-x 11 llg llg 196 9月 2 17:11 1-Brixton版教程示例
drwxr-xr-x 34 llg llg 4096 9月 2 15:57 2-Dalston版教程示例
-rw-r--r-- 1 root root 12288 4月 23 20:58 .android-studio.desktop.swp
drwxrwxr-x 10 llg llg 302 9月 13 22:23 dist
-rwsrwsrwt 1 llg llg 1968 5月 2 08:27 .keystore
drwxrwsr-x 5 llg llg 118 9月 14 08:38 llg
drwxrwsr-x 3 llg llg 35 9月 15 21:10 llg-centos-git-test-1
drwxrwsr-x 3 llg llg 80 9月 15 20:01 llg-test-git
drwxrwsr-x 5 llg llg 131 9月 7 20:13 llg-user-gateway
drwxrwsr-x 6 llg llg 151 9月 14 08:51 llg-web-springboot-class
drwxr-xr-x 39 llg llg 4096 9月 2 19:41 SpringCloudBook-master
drwx------ 14 llg llg 315 9月 3 07:46 spring-cloud-llg
drwxrwsr-x 5 llg llg 61 9月 5 18:53 untitled
drwxr-xr-x 12 llg llg 4096 6月 29 2016 计算机组成原理201407

 

测试从本地仓库推送回远程仓库

[llg@localhost llg-centos-git-test-1]$ vi llg.txt
[llg@localhost llg-centos-git-test-1]$ git add llg.txt
[llg@localhost llg-centos-git-test-1]$ git commit -m "测试"
[master 4bdb6c4] 测试
1 file changed, 1 insertion(+)
create mode 100644 llg.txt
[llg@localhost llg-centos-git-test-1]$ git status
# 位于分支 master
# 您的分支领先 'origin/master'1 个提交。
# (使用 "git push" 来发布您的本地提交)
#
无文件要提交,干净的工作区
[llg@localhost llg-centos-git-test-1]$ git push -u origin masterWarning: Permanently added the RSA host key for IP address '192.30.253.113' to the list of known hosts.
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 274 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@github.com:llgeill/llg-centos-git-test-1.git
07b4bcc..4bdb6c4 master -> master
分支 master 设置为跟踪来自 origin 的远程分支 master。

 

13.Git的分支管理

分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN。

如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN!

learn-branches

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

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

其他版本控制系统如SVN等都有分支管理,但是用过之后你会发现,这些版本控制系统创建和切换分支比蜗牛还慢,简直让人无法忍受,结果分支功能成了摆设,大家都不去用。

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

14.创建与合并分支管理

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

原理解析

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

git-br-initial

 

创建新分支 

当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev

git-br-create

git checkout命令加上-b参数表示创建分支并切换分支

 

[llg@localhost llg-test-git]$ git checkout -b dev
切换到一个新分支 'dev'
[llg@localhost llg-test-git]$

 

上面的命令等价与下面两条命令的结合

 

[llg@localhost llg-test-git]$ git branch dev
[llg@localhost llg-test-git]$ git checkout dev
切换到分支 'dev'

 

我们可以使用git branch 查看当前分支的情况 

 

[llg@localhost llg-test-git]$ git branch
* dev
master

 

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

新分支上提交版本库

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

git-br-dev-fd

 

 
[llg@localhost llg-test-git]$ vi one.txt
[llg@localhost llg-test-git]$ git add one.txt
[llg@localhost llg-test-git]$ git commit -m "在分支上提交版本信息"
[dev 8c8117b] 在分支上提交版本信息
1 file changed, 2 insertions(+)
[llg@localhost llg-test-git]$ git status
# 位于分支 dev
无文件要提交,干净的工作区
[llg@localhost llg-test-git]$

 

当我们重新切换到master分支时候发现one.txt根本没有变化,因为我们commit的是dev分支

 

[llg@localhost llg-test-git]$ git checkout master
切换到分支 'master'
[llg@localhost llg-test-git]$ vi one.txt
[llg@localhost llg-test-git]$ git checkout dev
切换到分支 'dev'
[llg@localhost llg-test-git]$ vi one.txt

 

合并分支

假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并 。注意下面的Fast-forwar 信息,Git告诉我们这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。当然,也不是每次合并都能Fast-forward,我们后面会讲其他方式的合并。

git-br-ff-merge

 

 
[llg@localhost llg-test-git]$ git checkout master
切换到分支 'master'
[llg@localhost llg-test-git]$ git merge dev
更新 3956cc3..8c8117b
Fast-forward
one.txt | 2 ++
1 file changed, 2 insertions(+)

 

删除分支

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

git-br-rm

 

 
[llg@localhost llg-test-git]$ git branch
dev
* master
[llg@localhost llg-test-git]$ vi one.txt
[llg@localhost llg-test-git]$ git branch -d dev
已删除分支 dev(曾为 8c8117b)。
[llg@localhost llg-test-git]$ git branch
* master

 

 小结

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

  • 查看分支:git branch
  • 创建分支:git branch <name>
  • 切换分支:git checkout <name>
  • 创建+切换分支:git checkout -b <name>
  • 合并某分支到当前分支:git merge <name>
  • 删除分支:git branch -d <name>

 

15.解决分支的冲突问题

首先创建一个分支并且提交了一次版本仓库,主分支也提交了一次版本仓库,如图所示

git-br-feature1

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

 

[llg@localhost llg-test-git]$ git merge fantasy
自动合并 dele
冲突(内容):合并冲突于 dele
自动合并失败,修正冲突然后提交修正的结果。

 

果然冲突了!Git告诉我们,readme.txt文件存在冲突,必须手动解决冲突后再提交。git status也可以告诉我们冲突的文件

 

[llg@localhost llg-test-git]$ git status
# 位于分支 master
# 您的分支领先 'origin/master'7 个提交。
# (使用 "git push" 来发布您的本地提交)
#
# 您有尚未合并的路径。
# (解决冲突并运行 "git commit")
#
# 未合并的路径:
# (使用 "git add <file>..." 标记解决方案)
#
# 双方修改: dele
#
修改尚未加入提交(使用 "git add" 和/或 "git commit -a"

 

手工修改冲突 

我们可以直接查看dele的内容,Git用<<<<<<<=======>>>>>>>标记出不同分支的内容

我们手动修改冲突位置,之后提交文件到暂存区并且commit

 

[llg@localhost llg-test-git]$ git add dele
[llg@localhost llg-test-git]$ git commit -m "cscs"
[master b1dd3cc] cscs

 

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

git-br-conflict-merged

查看分支合并情况 

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

 

* commit b1dd3cc1e90369e4fe2fc93f5a75b46a8918973f 
|\ Merge: 478f0bc 43149ab 
| | Author: llg <903857227@qq.com> 
| | Date: Sun Sep 16 15:10:03 2018 +0800 
| | 
| | cscs 
| | 
| * commit 43149abf7f5724e74f64b394521778ceaded39b1 
| | Author: llg <903857227@qq.com> 
| | Date: Sun Sep 16 15:01:24 2018 +0800 
| | 
| | ccc 
| | 
* | commit 478f0bc2b144ac6fce457e05c9e596e314bd1f11 
| | Author: llg <903857227@qq.com> 
| | Date: Sun Sep 16 15:02:12 2018 +0800 
| | 
| | ss 
| | 
* | commit a22d668c065ad42c38232850fad8082250aa63a2 
|\ \ Merge: 57e8b85 1575642 
| |/ Author: llg <903857227@qq.com> 
| | Date: Sun Sep 16 14:55:13 2018 +0800 
| |
| | Merge branch 'fantasy' 
| | 
| | ceshi 
| |
 
 

 

 

16.分支管理策略

通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。

如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。

下面我们实战一下--no-ff(禁用Fast forward)方式的git merge,一些前提条件略过  

 

[llg@localhost llg-test-git]$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
dele | 1 +
1 file changed, 1 insertion(+)
合并分支时,加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。
[llg@localhost llg-test-git]$ git log --graph --pretty=oneline --abbrev-commit
* 3a5456c merge with no-ff
|\
| * b68ba37 add merge
|/
* 0ed0fe3 测试
* b1dd3cc cscs
|\
| * 43149ab ccc
* | 478f0bc ss
* | a22d668 Merge branch 'fantasy'
|\ \
| |/
| * 1575642 xx
* | 57e8b85 Merge branch 'fantasy'
|\ \
| |/
| * 87bc5d7 ss
* | 709521d 版本冲突
* | 8c8117b 在分支上提交版本信息
|/
* 3956cc3 测试两个文件提交留一个文件不提交
* 44449dc 这次测试一次提交三个文件
* abde635 git练习测试

 

我们来看看不适用no-ff下的策略,发现会直接合并,分支信息不能再找到

 

[llg@localhost llg-test-git]$ git log --graph --pretty=oneline --abbrev-commit
* 8699b49 dele
* 3a5456c merge with no-ff
|\
| * b68ba37 add merge
|/
* 0ed0fe3 测试
* b1dd3cc cscs
|\
| * 43149ab ccc
* | 478f0bc ss
* | a22d668 Merge branch 'fantasy'
|\ \
| |/
| * 1575642 xx
* | 57e8b85 Merge branch 'fantasy'
|\ \
| |/
| * 87bc5d7 ss
* | 709521d 版本冲突
* | 8c8117b 在分支上提交版本信息
|/
* 3956cc3 测试两个文件提交留一个文件不提交
* 44449dc 这次测试一次提交三个文件
* abde635 git练习测试
 
可以看到,不使用Fast forward模式,merge后就像这样。

 

git-no-ff-mode

分支策略

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

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

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

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

所以,团队合作的分支看起来就像这样:

git-br-policy

 

17.解决Bug的临时分支 

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

当你接到一个修复一个代号101的bug的任务时,很自然地,你想创建一个分支issue-101来修复它,但是,等等,当前正在dev上进行的工作还没有提交:

 

 
[llg@localhost llg-test-git]$ git status
# 位于分支 master
# 您的分支领先 'origin/master'15 个提交。
# (使用 "git push" 来发布您的本地提交)
#
# 尚未暂存以备提交的变更:
# (使用 "git add <file>..." 更新要提交的内容)
# (使用 "git checkout -- <file>..." 丢弃工作区的改动)
#
# 修改: dele
#
修改尚未加入提交(使用 "git add" 和/或 "git commit -a"

 

 

并不是你不想提交,而是工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办?

封锁现场

幸好,Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作,再通过git status 查看发现工作区已经干净。 

 

[llg@localhost llg-test-git]$ git stash
Saved working directory and index state WIP on master: 7dec792 ss
HEAD 现在位于 7dec792 ss
[llg@localhost llg-test-git]$ git status
# 位于分支 master
# 您的分支领先 'origin/master'15 个提交。
# (使用 "git push" 来发布您的本地提交)
#
无文件要提交,干净的工作区
首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支

 

 

[llg@localhost llg-test-git]$ git checkout -b issue-101
切换到一个新分支 'issue-101'

 

现在修复bug,我们通过修改文件模拟这一个过程

 

[llg@localhost llg-test-git]$ vi delete.txt
[llg@localhost llg-test-git]$ git add delete.txt
[llg@localhost llg-test-git]$ git commit -m "ss"
[issue-101 e0b9de9] ss
1 file changed, 1 insertion(+)

 

修复完成后,切换到master分支,并完成合并,最后删除issue-101分支

 

[llg@localhost llg-test-git]$ git checkout master
切换到分支 'master'
您的分支领先 'origin/master'15 个提交。
(使用 "git push" 来发布您的本地提交)
[llg@localhost llg-test-git]$ git merge --no-ff -m "merge bug fix 101" issue-101
Merge made by the 'recursive' strategy.
delete.txt | 1 +
1 file changed, 1 insertion(+)
 

 

还原现场 

太棒了,原计划两个小时的bug修复只花了5分钟!

现在,是时候接着回到dev分支干活了,切换到dev分支然后用git stash list命令看看

 

[llg@localhost llg-test-git]$ git stash list
stash@{0}: WIP on master: 7dec792 ss

 

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

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

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

 

[llg@localhost llg-test-git]$ git stash pop
# 位于分支 master
# 您的分支领先 'origin/master'17 个提交。
# (使用 "git push" 来发布您的本地提交)
#
# 尚未暂存以备提交的变更:
# (使用 "git add <file>..." 更新要提交的内容)
# (使用 "git checkout -- <file>..." 丢弃工作区的改动)
#
# 修改: dele
#
修改尚未加入提交(使用 "git add" 和/或 "git commit -a")
丢弃了 refs/stash@{0} (fa2bbbe411ba20c94501e912ec120fa944c06b92)
再用git stash list查看,就看不到任何stash内容了 

 

 

[llg@localhost llg-test-git]$ git stash list
[llg@localhost llg-test-git]$

 

小结

  • 修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除;
  • 当手头工作没有完成时,先把工作现场git stash一下,然后去修复bug,修复后,再git stash pop,回到工作现场

 

18.强制删除分支

当需要开发一个新功能的时候,最好创建一个新的分支进行代码编辑,但是因为一些情况不需要这个分支了,但是这个分支从来没有合并过,所以要使用 git branch -D 的方式强制删除

 

[llg@localhost llg-test-git]$ vi feichuan.txt.swp
[llg@localhost llg-test-git]$ git add feichuan.txt.swp
[llg@localhost llg-test-git]$ git commit -m "ss"
[feature-vulcan 4cd8736] ss
1 file changed, 1 insertion(+)
create mode 100644 feichuan.txt.swp
[llg@localhost llg-test-git]$ git checkout master
M dele
切换到分支 'master'
您的分支领先 'origin/master'17 个提交。
(使用 "git push" 来发布您的本地提交)
[llg@localhost llg-test-git]$ git branch -D feature-vulcan
已删除分支 feature-vulcan(曾为 4cd8736)。

 

19.rebease整理分叉历史

在上一节我们看到了,多人在同一个分支上协作时,很容易出现冲突。即使没有冲突,后push的童鞋不得不先pull,在本地合并,然后才能push成功。

每次合并再push后,分支变成了这样

总之看上去很乱,有强迫症的童鞋会问:为什么Git的提交历史不能是一条干净的直线?

其实是可以做到的!

Git有一种称为rebase的操作,有人把它翻译成“变基”。

rebase

先不要随意展开想象。我们还是从实际问题出发,看看怎么把分叉的提交变成直线。

在和远程分支同步后,我们对hello.py这个文件做了两次提交。用git log命令看看:

 

 
$ git log --graph --pretty=oneline --abbrev-commit
* 582d922 (HEAD -> master) add author
* 8875536 add comment
* d1be385 (origin/master) init hello 
* e5e69f1 Merge branch 'dev' 
|\
| * 57c53ab (origin/dev, dev) fix env conflict
| |\
| | * 7a5e5dd add env
| * | 7bd91f1 add new env
...

 

注意到Git用(HEAD -> master)(origin/master)标识出当前分支的HEAD和远程origin的位置分别是582d922 add authord1be385 init hello,本地分支比远程分支快两个提交。

现在我们尝试推送本地分支:

 

$ git push origin master
To github.com:michaelliao/learngit.git
! [rejected] master -> master (fetch first)
error: failed to push some refs to 'git@github.com:michaelliao/learngit.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

 

很不幸,失败了,这说明有人先于我们推送了远程分支。按照经验,先pull一下: 

 

$ git pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 1), reused 3 (delta 1), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:michaelliao/learngit
d1be385..f005ed4 master -> origin/master
* [new tag] v1.0 -> v1.0
Auto-merging hello.py
Merge made by the 'recursive' strategy.
hello.py | 1 +
1 file changed, 1 insertion(+)

 

再用git status看看状态:

 

 
$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
(use "git push" to publish your local commits)
 
nothing to commit, working tree clean

 

加上刚才合并的提交,现在我们本地分支比远程分支超前3个提交。

git log看看:

 

$ git log --graph --pretty=oneline --abbrev-commit
* e0ea545 (HEAD -> master) Merge branch 'master' of github.com:michaelliao/learngit
|\
| * f005ed4 (origin/master) set exit=1
* | 582d922 add author
* | 8875536 add comment
|/
* d1be385 init hello
...

 

对强迫症童鞋来说,现在事情有点不对头,提交历史分叉了。如果现在把本地分支push到远程,有没有问题?

有!

什么问题?

不好看!

有没有解决方法?

有!

这个时候,rebase就派上了用场。我们输入命令git rebase试试:

 

 
$ git rebase
First, rewinding head to replay your work on top of it...
Applying: add comment
Using index info to reconstruct a base tree...
M hello.py
Falling back to patching base and 3-way merge..
Auto-merging hello.py
Applying: add author
Using index info to reconstruct a base tree...
M hello.py
Falling back to patching base and 3-way merge...
Auto-merging hello.py

 

 

 

 

输出了一大堆操作,到底是啥效果?再用git log看看:

 

 

 

$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master) add author
* 3611cfe add comment
* f005ed4 (origin/master) set exit=1
* d1be385 init hello

 

原本分叉的提交现在变成一条直线了!这种神奇的操作是怎么实现的?其实原理非常简单。我们注意观察,发现Git把我们本地的提交“挪动”了位置,放到了f005ed4 (origin/master) set exit=1之后,这样,整个提交历史就成了一条直线。rebase操作前后,最终的提交内容是一致的,但是,我们本地的commit修改内容已经变化了,它们的修改不再基于d1be385 init hello,而是基于f005ed4 (origin/master) set exit=1,但最后的提交7e61ed4内容是一致的。

这就是rebase操作的特点:把分叉的提交历史“整理”成一条直线,看上去更直观。缺点是本地的分叉提交已经被修改过了。

最后,通过push操作把本地分支推送到远程:

 

 
Mac:~/learngit michael$ git push origin master
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 576 bytes | 576.00 KiB/s, done.
Total 6 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 1 local object.
To github.com:michaelliao/learngit.git
f005ed4..7e61ed4 master -> master

 

再用git log看看效果:

$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master, origin/master) add author
* 3611cfe add comment
* f005ed4 set exit=1
* d1be385 init hello

 

 

 

远程分支的提交历史也是一条直线。

 

总结 

  • rebase操作可以把本地未push的分叉提交历史整理成直线;

  • rebase的目的是使得我们在查看历史提交的变化时更容易,因为分叉的提交需要三方对比。

20.什么是标签管理

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

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

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

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

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

如果换一个办法:

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

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

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

21.创建Git仓库版本标签

在Git中打标签非常简单,首先,切换到需要打标签的分支上

 

 
[llg@localhost llg-test-git]$ git branch
dev
issue-101
* master
[llg@localhost llg-test-git]$ git checkout dev
切换到分支 'dev'

 

创建当前版本标签

然后,敲命令git tag <name>就可以打一个新标签

[llg@localhost llg-test-git]$ git tag v1.0

创建其他版本标签

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

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

 

[llg@localhost llg-test-git]$ git log --pretty=oneline --abbrev-commit
8699b49 dele
3a5456c merge with no-ff
b68ba37 add merge
0ed0fe3 测试
b1dd3cc cscs
478f0bc ss
43149ab ccc
a22d668 Merge branch 'fantasy'
1575642 xx
57e8b85 Merge branch 'fantasy'
709521d 版本冲突
87bc5d7 ss
8c8117b 在分支上提交版本信息
3956cc3 测试两个文件提交留一个文件不提交
44449dc 这次测试一次提交三个文件
abde635 git练习测试
[llg@localhost llg-test-git]$ git tag
v1.0
[llg@localhost llg-test-git]$ git tag v0.9 3a5456c
[llg@localhost llg-test-git]$ git tag
v0.9
v1.0

 

查看所有标签 

 

[llg@localhost llg-test-git]$ git tag
v0.8
v0.9
v1.0

 

查看标签详细信息

注意,标签不是按时间顺序列出,而是按字母排序的。可以用git show <tagname>查看标签信息

[llg@localhost llg-test-git]$ git show v0.9
commit 3a5456c07d50d99437d01fbf538a41a4556d7504
Merge: 0ed0fe3 b68ba37
Author: llg <903857227@qq.com>
Date: Sun Sep 16 16:04:56 2018 +0800

merge with no-ff
 

创建带说明文字的标签

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

 

[llg@localhost llg-test-git]$ git tag -a v0.8 -m "version 0.8 released" b68ba37
[llg@localhost llg-test-git]$ git show v0.8
tag v0.8
Tagger: llg <903857227@qq.com>
Date: Mon Sep 17 09:31:27 2018 +0800
 
 
version 0.8 released
 
 
commit b68ba371b04d5b7b6305454c160d7ef88178ea2d
Author: llg <903857227@qq.com>
Date: Sun Sep 16 16:04:19 2018 +0800
 
 
add merge
 
 
diff --git a/dele b/dele
index 20c0828..a36d773 100644
--- a/dele
+++ b/dele
@@ -10,3 +10,4 @@ sad
end
 
 
1111111111111111111111
+2222222222222222222222

 

22.操作标签

删除标签

如果标签打错了,也可以删除

 

[llg@localhost llg-test-git]$ git tag -d v0.8
已删除 tag 'v0.8'(曾为 658e2ab)

 

推送某个标签到远程仓库 

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

如果要推送某个标签到远程,使用命令git push origin <tagname>

 

[llg@localhost llg-test-git]$ git push origin v1.0
Username for 'https://github.com': 903857227@qq.com
Password for 'https://903857227@qq.com@github.com':
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/llgeill/llg-centos-git--test.git
* [new tag] v1.0 -> v1.0

 

 推送所有标签到远程仓库 

或者,一次性推送全部尚未推送到远程的本地标签:

 

[llg@localhost llg-test-git]$ git push origin --tag
Username for 'https://github.com': 903857227@qq.com
Password for 'https://903857227@qq.com@github.com':
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/llgeill/llg-centos-git--test.git
* [new tag] v0.9 -> v0.9

 

删除远程标签

首先在本地删除标签

 

[llg@localhost llg-test-git]$ git tag -d v0.8
已删除 tag 'v0.9'(曾为 3a5456c)

 

然后,从远程删除。删除命令是 git push origin :refs/tags/xxx

 

[llg@localhost llg-test-git]$ git push origin :refs/tags/v0.8
Username for 'https://github.com': 903857227@qq.com
Password for 'https://903857227@qq.com@github.com':
To https://github.com/llgeill/llg-centos-git--test.git
- [deleted] v0.8

 

最后去官网github上查看tag是否被删除,发现确实被删除了

 

23.如何使用Github

我们一直用GitHub作为免费的远程仓库,如果是个人的开源项目,放到GitHub上是完全没有问题的。其实GitHub还是一个开源协作社区,通过GitHub,既可以让别人参与你的开源项目,也可以参与别人的开源项目。

在GitHub出现以前,开源项目开源容易,但让广大人民群众参与进来比较困难,因为要参与,就要提交代码,而给每个想提交代码的群众都开一个账号那是不现实的,因此,群众也仅限于报个bug,即使能改掉bug,也只能把diff文件用邮件发过去,很不方便。

但是在GitHub上,利用Git极其强大的克隆和分支功能,广大人民群众真正可以第一次自由参与各种开源项目了。

参与别人的开源项目 

如何参与一个开源项目呢?比如人气极高的bootstrap项目,这是一个非常强大的CSS框架,你可以访问它的项目主页https://github.com/twbs/bootstrap,点“Fork”就在自己的账号下克隆了一个bootstrap仓库,然后,从自己的账号下clone:

  

 

[llg@localhost 桌面]$ git clone git@github.com:llgeill/bootstrap.git
正克隆到 'bootstrap'...
remote: Counting objects: 126027, done.
remote: Compressing objects: 100% (40/40), done.
remote: Total 126027 (delta 44), reused 45 (delta 35), pack-reused 125952
接收对象中: 100% (126027/126027), 123.12 MiB | 4.97 MiB/s, done.
处理 delta 中: 100% (83420/83420), done.

 

一定要从自己的账号下clone仓库,这样你才能推送修改。如果从bootstrap的作者的仓库地址git@github.com:twbs/bootstrap.git克隆,因为没有权限,你将不能推送修改。

Bootstrap的官方仓库twbs/bootstrap、你在GitHub上克隆的仓库my/bootstrap,以及你自己克隆到本地电脑的仓库,他们的关系就像下图显示的那样:

github-repos

如果你想修复bootstrap的一个bug,或者新增一个功能,立刻就可以开始干活,干完后,往自己的仓库推送。

如果你希望bootstrap的官方库能接受你的修改,你就可以在GitHub上发起一个pull request。当然,对方是否接受你的pull request就不一定了。

 

23.如何使用码云

使用GitHub时,国内的用户经常遇到的问题是访问速度太慢,有时候还会出现无法连接的情况(原因你懂的)。

如果我们希望体验Git飞一般的速度,可以使用国内的Git托管服务——码云gitee.com)。

和GitHub相比,码云也提供免费的Git仓库。此外,还集成了代码质量检测、项目演示等功能。对于团队协作开发,码云还提供了项目管理、代码托管、文档管理的服务,5人以下小团队免费。

 码云的免费版本也提供私有库功能,只是有5人的成员上限。

使用码云和使用GitHub类似,我们在码云上注册账号并登录后,需要先上传自己的SSH公钥。选择右上角用户头像 -> 菜单“修改资料”,然后选择“SSH公钥”,填写一个便于识别的标题,然后把用户主目录下的.ssh/id_rsa.pub文件的内容粘贴进去:

 

已有本地仓库关联远程仓库 

如果我们已经有了一个本地的git仓库(例如,一个名为learngit的本地库),如何把它关联到码云的远程库上呢?

首先,我们在码云上创建一个新的项目,选择右上角用户头像 -> 菜单“控制面板”,然后点击“创建项目”:

创建远程仓库

关联远程仓库

由于刚才已经关联了github,如果用同一个tag orgin的话将会关联失败,所以我用了orgins 关联码云

 

[llg@localhost llg-test-git]$ git remote add origin git@gitee.com:eill/llg-test-git.git
fatal: 远程 origin 已经存在。
[llg@localhost llg-test-git]$ git push -u origin master
Username for 'https://github.com': 903857227@qq .com
Password for 'https://903857227@qq.com@github.com':
分支 master 设置为跟踪来自 origin 的远程分支 master。
Everything up-to-date

这样一来,我们的本地库就可以同时与多个远程库互相同步:

删除关联

[llg@localhost llg-test-git]$ git remote rm origin
[llg@localhost llg-test-git]$ git remote -v
origins git@gitee.com:eill/centos-git-gitee-test.git (fetch)
origins git@gitee.com:eill/centos-git-gitee-test.git (push)

码云也同样提供了Pull request功能,可以让其他小伙伴参与到开源项目中来。你可以通过Fork我的仓库:https://gitee.com/liaoxuefeng/learngit,创建一个your-gitee-id.txt的文本文件, 写点自己学习Git的心得,然后推送一个pull request给我,这个仓库会在码云和GitHub做双向同步。

24.自定义Git配置

安装Git一节中,我们已经配置了user.nameuser.email,实际上,Git还有很多可配置项。

开启命令行颜色

比如,让Git显示颜色,会让命令输出看起来更醒目

[llg@localhost llg-test-git]$  git config --global color.ui true

忽略特殊文件

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

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

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

忽略文件的原则是:

  1. 忽略操作系统自动生成的文件,比如缩略图等;
  2. 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;
  3. 忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。

举个例子:

假设你在Windows下进行Python开发,Windows会自动在有图片的目录下生成隐藏的缩略图文件,如果有自定义目录,目录下就会有Desktop.ini文件,因此你需要忽略Windows自动生成的垃圾文件:

# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini

然后,继续忽略Python编译产生的.pyc.pyodist等文件或目录:

# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build

加上你自己定义的文件,最终得到一个完整的.gitignore文件,内容如下:

 
# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini
 
# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build
 
 
# My configurations:
db.ini
deploy_key_rsa

最后一步就是把.gitignore也提交到Git,就完成了!当然检验.gitignore的标准是git status命令是不是说working directory clean

使用Windows的童鞋注意了,如果你在资源管理器里新建一个.gitignore文件,它会非常弱智地提示你必须输入文件名,但是在文本编辑器里“保存”或者“另存为”就可以把文件保存为.gitignore了。

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

 
$ git add App.class
The following paths are ignored by one of your .gitignore files:
App.class
Use -f if you really want to add them.

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

$ git add -f App.class

或者你发现,可能是.gitignore写得有问题,需要找出来到底哪个规则写错了,可以用git check-ignore命令检查:

$ git check-ignore -v App.class
.gitignore:3:*.class App.class

Git会告诉我们,.gitignore的第3行规则忽略了该文件,于是我们就可以知道应该修订哪个规则。

小结

  • 忽略某些文件时,需要编写.gitignore

  • .gitignore文件本身要放到版本库里,并且可以对.gitignore做版本管理!

配置别名

有没有经常敲错命令?比如git statusstatus这个单词真心不好记。

如果敲git st就表示git status那就简单多了,当然这种偷懒的办法我们是极力赞成的。

我们只需要敲一行命令,告诉Git,以后st就表示status(这个功能在linux上也有)

[llg@localhost llg-test-git]$ git config --global alias.st status
[llg@localhost llg-test-git]$ git st
# 位于分支 dev
# 尚未暂存以备提交的变更:
# (使用 "git add <file>..." 更新要提交的内容)
# (使用 "git checkout -- <file>..." 丢弃工作区的改动)
#
# 修改: dele
#
修改尚未加入提交(使用 "git add" 和/或 "git commit -a"

配置文件

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

配置文件放哪了?每个仓库的Git配置文件都放在.git/config文件中:

 
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = git@github.com:michaelliao/learngit.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
[alias]
last = log -1

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

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

$ cat .gitconfig
[alias]
co = checkout
ci = commit
br = branch
st = status
[user]
name = Your Name
email = your@email.com
配置别名也可以直接修改这个文件,如果改错了,可以删掉文件重新通过命令配置。

小结

给Git配置好别名,就可以输入命令时偷个懒。我们鼓励偷懒。

25.搭建Git服务器

远程仓库一节中,我们讲了远程仓库实际上和本地仓库没啥不同,纯粹为了7x24小时开机并交换大家的修改。

GitHub就是一个免费托管开源代码的远程仓库。但是对于某些视源代码如生命的商业公司来说,既不想公开源代码,又舍不得给GitHub交保护费,那就只能自己搭建一台Git服务器作为私有仓库使用。

搭建Git服务器需要准备一台运行Linux的机器,强烈推荐用Ubuntu或Debian,这样,通过几条简单的apt命令就可以完成安装。

假设你已经有sudo权限的用户账号,下面,正式开始安装。(在这里我使用的是Centos)

第一步,安装git

[llg@localhost llg-test-git]$ sudo yum install git

第二步,创建一个git用户,用来运行git服务

[llg@localhost llg-test-git]$ sudo adduser git

第三步,创建证书登录:

收集所有需要登录的用户的公钥,就是他们自己的id_rsa.pub文件,把所有公钥导入到/home/git/.ssh/authorized_keys文件里,一行一个。

第四步,初始化Git仓库:

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

[llg@localhost llg-test-git]$ sudo git init --bare sample.git
初始化空的 Git 版本库于 /home/llg/桌面/llg-test-git/sample.git/

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

[llg@localhost llg-test-git]$ sudo chown -R git:git sample.git

第五步,禁用shell登录:

出于安全考虑,第二步创建的git用户不允许登录shell,这可以通过编辑/etc/passwd文件完成。找到类似下面的一行:

git:x:1001:1001:,,,:/home/git:/bin/bash

改为:

git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell

这样,git用户可以正常通过ssh使用git,但无法登录shell,因为我们为git用户指定的git-shell每次一登录就自动退出。

第六步,克隆远程仓库:

现在,可以通过git clone命令克隆远程仓库了,在各自的电脑上运行:

 
[llg@localhost llg-test-git]$ git clone git@127.0.0.1:/llg-test-git/sample.git/

剩下的推送就简单了。

管理公钥

如果团队很小,把每个人的公钥收集起来放到服务器的/home/git/.ssh/authorized_keys文件里就是可行的。如果团队有几百号人,就没法这么玩了,这时,可以用Gitosis来管理公钥。

这里我们不介绍怎么玩Gitosis了,几百号人的团队基本都在500强了,相信找个高水平的Linux管理员问题不大。

管理权限

有很多不但视源代码如生命,而且视员工为窃贼的公司,会在版本控制系统里设置一套完善的权限控制,每个人是否有读写权限会精确到每个分支甚至每个目录下。因为Git是为Linux源代码托管而开发的,所以Git也继承了开源社区的精神,不支持权限控制。不过,因为Git支持钩子(hook),所以,可以在服务器端编写一系列脚本来控制提交等操作,达到权限控制的目的。Gitolite就是这个工具。

这里我们也不介绍Gitolite了,不要把有限的生命浪费到权限斗争中。

小结

  • 搭建Git服务器非常简单,通常10分钟即可完成;

  • 要方便管理公钥,用Gitosis

  • 要像SVN那样变态地控制权限,用Gitolite

26.总结

经过两天的学习,站在巨人的肩膀上,我把命令都执行了一遍,大概有了个印象。

Git虽然极其强大,命令繁多,但常用的就那么十来个,掌握好这十几个常用命令,你已经可以得心应手地使用Git了。

友情附赠国外网友制作的Git Cheat Sheet,建议打印出来备用:

Git Cheat Sheet

Git的官方网站:http://git-scm.com

 

 

 

 

 

 


作者:廖雪峰
链接:https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000
來源:廖雪峰的官方网站

 

posted on 2018-09-17 13:41  liliguang  阅读(1001)  评论(0编辑  收藏  举报