Git 分布式版本控制
本篇系统讲解 Git 的来源与用途、版本控制的基本概念(工作区 / 暂存区 / 版本库)、安装与配置、版本库初始化、文件的添加与提交、状态查看、差异对比、版本回退、撤销修改、删除文件与恢复,并介绍基于 GitLab 搭建私有远程版本库的过程,以及远程仓库的添加、克隆、推送、多远程库与多 SSH Key 的管理,最后附本地与远程常用命令汇总。
一、Git 简介
1.1 从哪儿来
Git 出身高贵,出自 Linus Torvalds 之手,仅用了两个礼拜就开发出来了。
Linus Torvalds 也是 Linux 内核的创始人。
1.2 有什么用
Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。
1.3 什么是分布式版本控制系统
平时写一个 "Hello World" 程序,或者只有几百行代码的小项目,不需要专门的代码管理工具,依靠记忆就能把代码捋顺。
但一旦涉及代码量巨大的项目,往往需要很多人协同工作数周乃至数月才能完成。开发过程中会面临代码的修改、增删、恢复等工作,开发人员不可能清楚地记得每次变化,这时就需要借助版本管理工具来跟踪代码的变化历程。
版本管理工具给每个文件赋予一个版本号,每次修改之后(哪怕只改动了一个字母),都会精确地记录下来并改变该文件的版本号。这样,每个版本号就对应着文件的一次变化,依此可以进行文件的对比、恢复等操作。
文件在 Git 中的流转可以概括为:
vim a.txt ---> 暂存区(add) ---> 版本库(commit)
修改其中某一行 修改其中某一行
1.4 常见的分布式版本控制系统
- SVN
- VCS
- Git
- ……
二、Git 的安装
2.1 安装 Git 及其依赖包
yum install -y curl-devel expat-devel gettext-devel openssl-devel zlib-devel git-core
2.2 查看安装的 Git 版本
git --version
输出示例:
git version 1.8.3.1
能看到版本信息,说明已经安装完成。
三、Git 的配置文件
Git 有三级配置文件,作用范围从大到小依次为:
| 配置文件 | 作用范围 | 对应命令参数 |
|---|---|---|
/etc/gitconfig |
全局配置文件(主配置文件),对所有用户都有效 | git config --system |
~/.gitconfig |
局部配置文件,仅对当前用户有效 | git config --global |
工作目录中的 .git/config |
仅对当前工作目录有效,存在时会覆盖以上两个文件的效果 | git config(仓库内) |
Git 查找配置的顺序为:
./.git/config --> ~/.gitconfig --> /etc/gitconfig
列出当前 Git 的所有配置详情:
git config --list
有时会出现重复选项,这是因为相同配置来自于多个配置文件。
3.1 设置用户名和邮箱
使用 Git 提交前必须配置 user.name 和 user.email,否则无法提交:
git config --global user.name "username"
git config --global user.email "you@example.com"
--global表示写入~/.gitconfig,对当前用户的所有仓库生效。- 若不加
--global(在某个仓库目录内执行),则只写入该仓库的.git/config。
四、Git 版本库的创建
先创建一个目录,用作 Git 仓库:
mkdir /testgit
将该目录初始化为 Git 仓库,有两种写法。
写法一:进入目录后初始化当前目录:
cd /testgit
git init
写法二:直接初始化指定目录:
git init /testgit
初始化完成后,会在指定目录内自动生成一个 .git 目录,用于存放 Git 必备的文件,该目录下的文件请勿随意修改:
ls -a /testgit
. .. .git
五、Git 文件上传(add 与 commit)
5.1 两个核心命令
git add filename # 将文件添加到暂存区(临时存放),此时并未真正修改版本库
git commit -m '备注信息' # 将暂存区的内容提交到版本库,此时才会真正修改版本库
-m后面的备注信息为自定义内容。为了后期更好查阅,建议每次都填写,并尽量写得详细些。Git 默认要求提交必须带备注信息。
5.2 完整示例
创建一个即将上传的文件:
echo 'test' > a.txt
将文件添加到暂存区:
git add a.txt
注意:上传的文件必须位于你创建的仓库目录中,否则会报致命错误。可以多次
add后再执行一次commit,每次可添加多个文件,以空格分隔,例如git add a.txt b.txt。
提交之前的所有操作(新增、修改、删除):
git commit -m "添加新文件a.txt"
如果此时还没有配置用户名和邮箱,将无法正常提交,Git 会提示类似 "Please tell me who you are" 的信息。需要先配置:
git config user.name 'test'
git config user.email 'test@localhost'
查看当前用户配置信息:
git config --list
user.name=test
user.email=test@localhost
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
再次提交即可成功:
git commit -m '新增文件a.txt'
[master(根提交) 61be9a1] 新增文件a.txt
1 file changed, 1 insertion(+)
create mode 100644 a.txt
六、查看状态、差异与日志("后悔药")
6.1 修改文件并查看状态
修改 a.txt,新增一行:
vim a.txt
文件内容变为:
test
test2
使用 git status 查看当前状态:
git status
# 位于分支 master
# 尚未暂存以备提交的变更:
# (使用 "git add <file>..." 更新要提交的内容)
# (使用 "git checkout -- <file>..." 丢弃工作区的改动)
#
# 修改: a.txt
#
修改尚未加入提交(使用 "git add" 和/或 "git commit -a")
6.2 用 diff 对比改动
git diff a.txt
diff --git a/a.txt b/a.txt
index 9daeafb..259b241 100644
--- a/a.txt
+++ b/a.txt
@@ -1 +1,2 @@
test
+test2
输出会清楚地告诉你哪里新增了一行(以 + 标识)。
确认改动无误后,就可以 add 添加到暂存区。注意 add 只是加入暂存区,不会修改版本库内容,需要 commit 后才会真正写入版本库。
6.3 添加到暂存区并再次查看状态
git add a.txt
git status
# 位于分支 master
# 要提交的变更:
# (使用 "git reset HEAD <file>..." 撤出暂存区)
#
# 修改: a.txt
#
此时系统提示修改已经加入暂存区,但还未正式提交至版本库。确认无误后执行 commit:
git commit -m '修改a.txt,新增一行'
[master a5178c8] 修改a.txt,新增一行
1 file changed, 1 insertion(+)
再次查看状态,会提示工作区干净,因为所有操作都已经提交:
git status
# 位于分支 master
无文件要提交,干净的工作区
6.4 查看提交日志
通过 git log 可以看到全部提交记录,包含版本号、提交用户、提交时间和备注:
git log
commit a5178c8db1d0d65fb2345dafd87a012451ffaad8
Author: test <test@localhost>
Date: Fri Oct 5 17:00:59 2018 +0800
修改a.txt,新增一行
commit 0a600a215dff87d5c4a554081082530ed24b1da0
Author: test <test@localhost>
Date: Fri Oct 5 16:51:17 2018 +0800
添加新文件a.txt
每次
commit时填写的备注都会记录到日志中,所以 Git 默认要求必须带备注信息。
如果觉得上面的内容太冗长,可以只显示版本号和备注:
git log --pretty=oneline
a5178c8db1d0d65fb2345dafd87a012451ffaad8 修改a.txt,新增一行
0a600a215dff87d5c4a554081082530ed24b1da0 添加新文件a.txt
前面一长串字符串为 Git 的版本号(commit id);后面为提交时填写的备注信息。
6.5 版本回退相关命令
git log # 查看提交日志,可以看到都提交了哪些内容
git reset --hard HEAD^ # 回退到上一个版本(Git 自动回到上一次提交前的版本)
git reset --hard [commit id] # 回退到指定的版本(commit id 可通过 git log 确定)
git reflog # 查看所有历史操作记录(回退错误时可用来找回 commit id)
说明:
HEAD表示当前版本,HEAD^表示上一个版本,HEAD^^表示上上个版本,以此类推;也可以写成HEAD~1、HEAD~2。- commit id 不需要写全,写前几位即可,Git 会自动补全,但位数不能太少以免产生歧义。
6.6 回退示例
当前 a.txt 有三行内容:
cat a.txt
test
test
test
查看日志:
git log
commit 3a7c1eb28243b8ef7ffda0455b1c665bec15a2e4
Author: test <test@localhost>
Date: Fri Oct 5 17:23:05 2018 +0800
第二次修改a.txt
commit a5178c8db1d0d65fb2345dafd87a012451ffaad8
Author: test <test@localhost>
Date: Fri Oct 5 17:00:59 2018 +0800
修改a.txt,新增一行
commit 0a600a215dff87d5c4a554081082530ed24b1da0
Author: test <test@localhost>
Date: Fri Oct 5 16:51:17 2018 +0800
添加新文件a.txt
如果想回到 "修改a.txt,新增一行" 这个版本,自动回退一个版本:
git reset --hard HEAD^
HEAD 现在位于 a5178c8 修改a.txt,新增一行
回退后文件恢复到第二次修改前的样子:
cat a.txt
test
test
同时日志也会发生变化(看不到 "第二次修改a.txt" 那条记录了):
git log
commit a5178c8db1d0d65fb2345dafd87a012451ffaad8
Author: test <test@localhost>
Date: Fri Oct 5 17:00:59 2018 +0800
修改a.txt,新增一行
commit 0a600a215dff87d5c4a554081082530ed24b1da0
Author: test <test@localhost>
Date: Fri Oct 5 16:51:17 2018 +0800
添加新文件a.txt
恢复到 "第二次修改a.txt"
如果当前会话没有关闭,可以从之前的输出中找到第二次修改的 commit id 进行恢复。commit id 不用写全,写前几位即可,Git 会自动补全,但位数不能太少:
git reset --hard 3a7c1eb
HEAD 现在位于 3a7c1eb 第二次修改a.txt
再次查看文件,又回到了第二次修改后的模样:
cat a.txt
test
test
test
如果当前终端已经关闭,无法通过历史输出找回 commit id,可以通过 git reflog 查找历史操作记录:
git reflog
e87bbb6 HEAD@{0}: reset: moving to HEAD^
d582500 HEAD@{1}: commit: 第二次修改a.txt
e87bbb6 HEAD@{2}: commit: 第一次修改a.txt
......
每次操作都会被记录下来,第一个字段即为该次操作的版本号。
找到版本号后直接恢复:
git reset --hard e87bbb6
HEAD 现在位于 e87bbb6 第一次修改a.txt
七、工作区与暂存区的作用
三者的关系如下:
工作区(仓库目录) --add--> 暂存区(临时存放) --commit--> master(版本库,永久存放)
由上可知:当对一个文件执行 git add filename 后,文件被修改的内容会存放到暂存区,不会直接修改版本库(master)的内容;只有执行 git commit 后,Git 才会将修改写入版本库。
值得注意的是,Git 的特性是管理文件的修改,而不是直接管理文件。所以如果你对同一个文件进行了多次修改,每次修改后都需要重新 add 才能把最新改动写入暂存区。
7.1 多次修改示例
第一次修改 a.txt,新增 test1:
vim a.txt
test
test
test1
添加到暂存区:
git add a.txt
第二次修改 a.txt,再新增 test2:
vim a.txt
test
test
test1
test2
直接提交(注意:第二次修改并没有再次 add):
git commit -m '第三次修改a.txt'
[master 2d5e028] 第三次修改a.txt
1 file changed, 1 insertion(+)
提交后查看状态:
git status
# 位于分支 master
# 尚未暂存以备提交的变更:
# (使用 "git add <file>..." 更新要提交的内容)
# (使用 "git checkout -- <file>..." 丢弃工作区的改动)
#
# 修改: a.txt
#
修改尚未加入提交(使用 "git add" 和/或 "git commit -a")
可以看到 Git 仍然提示 a.txt 有一次新的修改未被提交,因为最后一次修改后并没有再次 add,所以这次改动没有进入暂存区。
使用 git diff HEAD 对比工作区与当前版本库的内容,会发现后面添加的一行确实没有提交:
git diff HEAD -- a.txt
diff --git a/a.txt b/a.txt
index 0867e73..d0c7fbe 100644
--- a/a.txt
+++ b/a.txt
@@ -1,3 +1,4 @@
test
test
test
+test2
总结:如果需要对工作区中的多个文件进行修改,建议先把需要修改的内容都改好后,再统一
add,然后commit,这样可以省去多次add和多次提交。
八、撤销修改
修改 a.txt,新增了一行错误内容 tset:
vim a.txt
test
test
test
test
tset
保存后发现新增或修改的内容有误,需要撤销后重新编辑,可以使用:
git checkout -- filename
当然也可以直接手动修改文件内容。注意命令中必须要有
--,如果没有--,则表示切换分支,含义完全不同。
该命令会有两种恢复情况:
- 如果修改后还未提交到暂存区(未
add),则将文件内容恢复到与版本库相同的状态。 - 如果修改后已经提交到暂存区(已
add),则将文件内容恢复到与暂存区相同的状态。
8.1 未 add 之前的撤销
git checkout -- a.txt
cat a.txt
test
test
test
test
之前新增的那行错误内容已经消失。
8.2 已经 add 之后的撤销
先用 git reset HEAD 将暂存区中保存的临时修改撤出,回到工作区:
git reset HEAD a.txt
重置后撤出暂存区的变更:
M a.txt
再使用 git checkout -- 撤销工作区中文件的修改:
git checkout -- a.txt
cat a.txt
test
test
test
test
8.3 错误内容已经提交到版本库怎么办
如果不小心把错误内容写入了版本库,可以通过版本回退来恢复:
git reset --hard HEAD^
# 或者
git reset --hard [commit id]
注意:上述恢复仅限尚未推送到远程版本库的提交。一旦推送到远程版本库,撤销起来将会非常麻烦。
九、删除文件与版本库文件
一般情况下,不需要的文件直接 rm 掉即可。Git 也会监控文件的删除操作,如果有删除,Git 会告诉你删除了哪些文件。
9.1 示例
新建并提交一个文件 b.txt:
touch b.txt
git add b.txt
git commit -m '新建文件b.txt'
直接删除文件:
rm -f b.txt
查看状态:
git status
# 位于分支 master
# 尚未暂存以备提交的变更:
# (使用 "git add/rm <file>..." 更新要提交的内容)
# (使用 "git checkout -- <file>..." 丢弃工作区的改动)
#
# 删除: b.txt
#
修改尚未加入提交(使用 "git add" 和/或 "git commit -a")
Git 会明确地告诉你删除了哪个文件。
9.2 确实需要删除:从版本库一并删除
git rm b.txt
rm 'b.txt'
然后提交,将删除操作同步到版本库:
git commit -m '删除文件b.txt'
[master 95015c7] 删除文件b.txt
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 b.txt
9.3 删错了需要恢复
如果是不小心删错了文件,而该文件又必须恢复:
git checkout -- b.txt
因为此时版本库中还有 b.txt,所以可以很轻松地恢复。
注意:使用版本库恢复文件时,恢复的是最近一次提交到版本库的版本,所以该次提交之后对文件所做的修改将会丢失。
十、私有版本库 GitLab
前面使用的 GitHub 是别人提供的服务,免费版本只有公开版本库,任何人都可以看到你的代码。如果想拥有私有版本库:
- 花钱购买;
- 自己搭建。
GitLab 是一款开源的分布式存储管理系统,可以让你轻松搭建自己的 Git 远程版本库,并免费拥有私有版本库。
10.1 GitLab 提供的功能
- 用户管理;
- 组管理;
- 私有版本库;
- 公开版本库等。
10.2 GitLab 安装过程
安装前准备
- 绑定主机名;
- 绑定 hosts;
- 关闭 iptables 和 selinux;
- 停止所有占用 80 和 8080 端口的服务。
1. 下载软件包
清华镜像源(下载偏慢):
wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gitlab-ce-10.0.6-ce.0.el7.x86_64.rpm
也可以使用局域网内已下载好的软件包:
wget http://192.168.2.254/automation/git/gitlab-ce-10.0.6-ce.0.el7.x86_64.rpm
2. 安装依赖
yum install -y curl policycoreutils-python openssh-server openssh-clients
3. 安装 GitLab(使用本地 rpm 包)
yum localinstall -y git/gitlab-ce-10.0.6-ce.0.el7.x86_64.rpm
由于包比较大,安装过程会慢一些。
4. 修改 GitLab 配置
编辑主配置文件:
vim /etc/gitlab/gitlab.rb
把第 13 行的 IP 换成你自己的 IP 地址,否则后面添加用户时会出现无法设置密码的情况。IP 前面必须有 http://,否则会报错:
external_url 'http://3.3.3.6'
以下为配置邮件发送的内容,在文件末尾添加:
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.qq.com"
gitlab_rails['smtp_port'] = 465
gitlab_rails['smtp_user_name'] = "******@qq.com" # 你的 QQ 邮箱地址
gitlab_rails['smtp_password'] = "授权码" # 在 QQ 邮箱中获取
gitlab_rails['smtp_domain'] = "smtp.qq.com"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = true
gitlab_rails['smtp_tls'] = true
gitlab_rails['gitlab_email_from'] = "******@qq.com" # 你的 QQ 邮箱地址
5. 初始化 GitLab
gitlab-ctl reconfigure
初始化过程比较慢。
6. 启动 GitLab 服务
GitLab 服务管理命令:
gitlab-ctl start # 启动 GitLab 服务
gitlab-ctl status # 查看 GitLab 的状态
gitlab-ctl stop # 停止 GitLab 服务
7. 查看端口监听情况
GitLab 默认监听 80 和 8080 两个端口:
lsof -i:80
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 5453 root 7u IPv4 38573 0t0 TCP *:http (LISTEN)
nginx 5454 gitlab-www 7u IPv4 38573 0t0 TCP *:http (LISTEN)
lsof -i:8080
8. 访问 Web 界面
启动后,在浏览器中输入服务器的 IP 地址即可访问 GitLab 页面,按页面提示进行后续初始化操作。详情可参见配套的 "github 与 gitlab 使用说明" 文档。
十一、GitLab 远程版本库的使用
GitLab 的使用过程与 GitHub 非常相似,都可以通过多种协议进行推送和克隆。
11.1 HTTP 协议方式
添加远程版本库:
git remote add origin https://IP/zstest/test.git
11.2 SSH 协议方式
生成 SSH 密钥对(输入命令后一路回车即可):
ssh-keygen -t rsa -C "你自己的邮箱地址"
查看公钥内容,并将其复制粘贴到 GitLab/GitHub 中:
cat ~/.ssh/id_rsa.pub
添加远程版本库:
git remote add origin_2 git@IP:test/test.git
将本地版本库内容推送到自己搭建的远程版本库:
git push origin_2 master
11.3 一台服务器使用多个远程版本库
备注:添加第二个 SSH Key 之后,必须将第二个版本库删除后重建,才能正常推送。
注意:一个 SSH Key 只能供一个版本库使用。如果一台服务器需要使用多个版本库,直接生成多个 SSH Key 即可:
ssh-keygen -t rsa -C '邮箱地址'
在提示输入保存路径时,设置不同的文件名:
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): /root/.ssh/gitlab_rsa
......
启动 ssh-agent 并添加新的私钥:
eval `ssh-agent -s`
ssh-add ~/.ssh/gitlab_rsa
准备好后,将新的公钥内容复制到对应平台即可。
11.4 远程版本库的查看与删除
列出当前已添加的所有远程版本库:
git remote
以长格式列出当前所有远程版本库(含抓取和推送地址):
git remote -v
删除指定的远程版本库:
git remote remove test
11.5 远程版本库的下载(克隆)
将指定服务器上的远程版本库克隆到本地当前目录:
git clone git@IP:username/test.git
例如:
git clone git@3.3.3.8:root/test.git
该命令将 3.3.3.8 上用户 root 的 test 远程版本库克隆到本地。
十二、本章命令总结
12.1 本地版本库操作命令
| 命令 | 说明 |
|---|---|
git init |
初始化本地版本库 |
git add filename |
将文件添加到暂存区 |
git commit -m '备注信息' |
将暂存区内容提交到版本库永久存放区 |
git status |
查看 Git 版本库当前状态 |
git diff filename |
将工作区中的文件与版本库中的文件进行对比 |
git log |
查看 Git 操作(提交)日志 |
git reflog |
查看 Git 历史操作记录(恢复版本时会用到) |
git reset --hard HEAD^ |
将本地版本库回退到上一个版本 |
git reset --hard [版本号] |
将本地版本库回退到指定版本 |
git checkout -- filename |
撤销工作区中文件的修改 |
git rm filename |
删除版本库永久存放区中的指定文件 |
关于 git checkout -- filename 的两种恢复情况:
- 如果修改后还未提交到暂存区,则将文件内容恢复到与版本库相同的状态;
- 如果修改后已经提交到暂存区,则恢复文件内容到与暂存区相同的状态。
12.2 远程版本库操作命令
| 命令 | 说明 |
|---|---|
git remote |
查看当前版本库已经添加的所有远程版本库 |
git remote -v |
长格式查看所有已添加的远程版本库 |
git remote add [版本库名称] [链接地址] |
添加一个远程版本库 |
git remote remove [版本库名称] |
删除一个远程版本库 |
git push -u [版本库名称] [分支名称] |
将本地版本库内容推送到指定远程版本库 |
git clone [版本库链接地址] |
克隆(下载)一个远程版本库的内容到本地 |
补充:从远程版本库获取更新可以使用
git pull [版本库名称] [分支名称],它相当于git fetch+git merge,用于把远程分支的改动拉取并合并到当前分支。
此处评论已关闭