Git 简易使用手册

 Technique  comment

Git 是一个免费且开源的分布式版本控制系统,可以快速、高效地处理各类项目。

版本控制简单而言就是记录每次修改的内容及时间,并且提供版本回滚功能。分布式就是可以多人协作修改同一个项目,为大型项目同时进行开发。

Git 的诞生

Git 最初由林纳斯·托瓦兹(Linus Torvalds)开发,于 2005 年以 GPL 协议发布。最初的目的是为了更好地管理 Linux 的内核开发而设计。

琐事

此部分为 Git 的起源,阅读时间 3 分钟,可选内容。

1999年12月,Linux PowerPC 项目首先开始使用 BitKeeper - 这个非开源但是有条件免费的版本控制工具。到了 2002 年 2 月,Linux 之父 Linus Torvalds 决定开始用它来管理Linux 内核代码。Linus 对 BitKeeper 的评价是 "The best tool for the job",这是个在开源社区内外都引起了广泛瞩目的举动。

BitMover 是 BitKeeper 的开发厂商,创始人和 CEO 是 Larry McVoy。Larry 期望BitKeeper 能帮助 Linus 免于陷入不断加重的 Linux 内核管理工作中。事实上,自从 Linus 3 年前开始使用 BitKeeper 之后,Linux 的开发步伐加快了两倍。

Free Software 这个短语中的 Free 可以被理解成“自由”,好像 Free as in Freedom 中的Free 或者仅仅是“免费”,Free as in Free Beer 中的 Free。BitKeeper 是按照后一种定义免费可用的。允许 Free / Open source 软件开发者不用付费就能使用这个工具 - 前提是这样一个协议:这个免费工具的真正使用者不能同时开发其竞争产品。换句话说,这个工具可以免费 地使用(freely used),不能被随便克隆(freely cloned)。当然同时,BitMover 还有一个更高级的 BitKeeper 版本是商业产品,需要购买。这两个版本都是 BitMover 的知识产权。

然而在 2005 年,开源社区的某些开发者对 BitKeeper 的一些功能进行反向工程的举动引起了BitMover 的多次注意和警告,最近两次这样的动作则最终导致了 BitMover 决定终止结束免费BitKeeper 产品的开发和应用,收回 Linux 社区的免费使用权。

于是在此情况下 Linus 花了两周时间用 C 语言写了一个分布式的版本控制系统,就是大名鼎鼎的 Git ,在之后的一个月中,Linux 的源码便转移交由 Git 进行管理。

在 2008 年,GitHub 上线了,为开源项目免费提供 Git 存储,很多开源项目迁移至 GitHub,比如:jQuery,PHP,Ruby 等。2018 年 6 月 4 日,微软宣布以 75 亿美元的价格已完成对 GitHub 的收购。2019 年 1 月 7 日,GitHub 宣布支持免费账户也可以创建无限私人项目了,但是需要注意,协作者最多 3 人。

优势

相对于 CVS 及 SVN 都是集中式的版本控制系统,而 Git 是分布式版本控制系统。

集中式和分布式有什么区别?举个例子来说明,集中式版本库是一个图书馆(中心服务器),若两个人想修改内容需要分别先借出来(拉取完整代码),修改后归还至图书馆(推送至中心服务器),然后另一个用户再借出来新的版本,再进行修改。分布式则完全相反,每个用户都是一个完整的版本库,每个用户修改文件后只需要将修改部分推送到对方即可。

安装与配置

Linux

在 Linux 系统上,可以直接使用包管理工具进行安装。

# yum install git
# apt install git
# zypper install git
# pacman -S git

macOS

Windows

配置账户

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

小贴士:--global 参数表示全局参数,全部的仓库都使用,也可以对单独的仓库配置。

本地仓库入门

前面一直提到的版本库,英文名 repository ,可以简单理解为一个目录,在此目录下的全部文件都将被 Git 管理,可以追踪每个文件的修改及修改者等等。

创建仓库

$ mkdir example

初始化仓库

初始化的过程实际上就是让 Git 对此目录进行标记,在初始化后对此文件的修改会被 Git 跟踪。

$ cd example
$ git init

小贴士:若使用 Windows 系统,请注意尽量避免使用在完整路径中包括中文名的路径,以防出现意外问题。

初始化后这就是一个干净的工作目录,可以发现目录下多了一个隐藏文件夹 .git ,其中的文件记录了本目录的全部信息,请勿修改。

额外一点,Git 的版本控制只能追踪文本型文件的修改记录,图片、视频、二进制文件(软件专属格式)等无法进行追踪,只能记录文件的变化。

添加文件至仓库

直接在初始化的目录中创建文件或者将已有文件移动进来即可被 Git 跟踪。

$ touch README.md

添加文件后需要通知 Git 将文件添加到缓存区。

$ git add README.md

小贴士:本步骤没有输出结果,所谓没有结果就是最好的结果。

从仓库删除文件

若不小心将文件复制到了工作目录中,并已经提交。现在想删除,若只删除工作区的文件是不行的,仓库中还是存在此文件的。

$ git rm 文件名

如果删除之前修改过并且已经放到暂存区域的话,则

$ git rm -f 文件名

如果把文件从暂存区域移除,但仍然希望保留在当前工作目录中,换句话说,仅是从跟踪清单中删除

$ git rm --cached 文件名

提交到仓库

$ git commit -m "Add a README file"

输出结果

[master (root-commit) a226e4f] 1
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 README.md

小贴士:-m 参数为本次提交的说明内容,可以随意填写,但是强烈建议使用可读的有意义的说明进行提交。以便日后分辨本次修改内容。

忽略说明内容的提交【不建议使用】

$ git commit -a

修改文件内容

$ echo "This is README file" > README.md

查看工作区状态

$ git status

可以看到 README.md 被修改过了,但是还没有提交。

比较文件区别

$ git diff README.md

小贴士:使用此命令可以比较文件的改动,一般以+开头的绿色行为添加的内容,以-开头的红色行为删除的内容。

注意此命令默认比较的是 暂存区工作区 的区别。
git diff --cached 比较的是 主分支暂存区 的区别
git diff HEAD 比较的是 主分支工作区 的区别

输出结果

diff --git a/README.md b/README.md
index e69de29..e48f963 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1 @@
+This is README file

注意文件修改后需要重新添加到缓冲区,即

$ git add .

小贴士:使用 . 可以将本目录下的文件全选加入缓冲区,防止多个文件添加需要制定多个文件执行多次,日常也推荐直接使用此命令

再次提交即可

$ git commit -m "Add Content"

输出结果

[master 0995a5a] Add Content
 1 file changed, 1 insertion(+)

每次修改文件后都会提交,这样就会积累了一堆版本,这个版本可以理解为【游戏存档】,那么存档的作用是什么,显而易见那就是回滚

查看仓库历史

$ git log

注意:其他参数 --graph 图形模式 --abbrev-commit 完整信息模式。

输出结果

commit 4eba501d7abf7d3525ffec6d493070f517098f01 (HEAD -> master)
Author: Vndroid <diracsvip@gmail.com>
Date:   Thu Feb 28 11:23:01 2019 +0800

    Edit Content

commit 0995a5a381b8c7e0d02219a854904316d9aa808c
Author: Vndroid <diracsvip@gmail.com>
Date:   Thu Feb 28 10:19:36 2019 +0800

    Add Content

commit a226e4f97e5538d13b51e6cffd1ef9c882ab54a0
Author: Vndroid <diracsvip@gmail.com>
Date:   Wed Feb 27 18:03:21 2019 +0800

    1

若只想看到简洁版本,可以加上参数 --pretty=oneline

输出结果

4eba501d7abf7d3525ffec6d493070f517098f01 (HEAD -> master) Edit Content
0995a5a381b8c7e0d02219a854904316d9aa808c Add Content
a226e4f97e5538d13b51e6cffd1ef9c882ab54a0 1

在结果中,最前方的字符串是 Commit ID,使用这个 ID 即可回滚到某个版本,HEAD 这个符号标记了当前的版本,可以理解为一个指针。

回滚历史版本

$ git reset --hard COMMIT_ID

小贴士:使用 Commit ID 可以精确回滚到某个版本,也可以使用 HEAD^(上个版本)HEAD^^(上上个版本)HEAD-100(前一百个版本),不推荐使用相对版本,推荐使用ID号回滚。此时就会发现每次提交的描述就会很重要,可以根据版本描述进行精确回滚,因此不建议使用跳过描述的参数进行提交。

$ git reset --hard a226e4f97e5538d13b51e6cffd1ef9c882ab54a0

输出结果

HEAD is now at a226e4f 1

回滚后悔药

若发现回滚版本错误,或者想恢复之前的版本,可以使用命令查看变更日志。

$ git reflog

输出结果

a226e4f (HEAD -> master) HEAD@{0}: reset: moving to a226e4f97e5538d13b51e6cffd1ef9c882ab54a0
0995a5a HEAD@{1}: commit: Add Content
a226e4f (HEAD -> master) HEAD@{2}: commit (initial): 1

可以看到从版本 0995a5a 回滚到了版本 a226e4f ,因此只要再次回滚到版本 0995a5a 即可。

小贴士:可以看到系统使用的不是完整的 commit id ,而是前面的一段,实际上我们也可以使用缩略写法,使用 ID 的前任意几位,但是必须保证这个 ID 的前几位是唯一的。

工作区误操作

$ git checkout -- 文件名

即可将工作区的文件修改丢弃

$ git reset HEAD 文件名

先将其恢复分支版本,然后

$ git checkout -- 文件名
$ git checkout -- 文件名

即可将文件还原为版本库中的版本,如果期间有修改但是未提交会丢失。

名次解释

工作区和暂存区

上面的说明中可以看到有暂存区和工作区等概念。

工作区就是指用户最初初始化的那个文件夹,也就是可以在电脑中能看到的那个文件夹。

版本库(仓库)就是 Git 建立的帮助用户控制版本的存储区,其中存储了用户对文件的全部修改记录。

版本库中分为两个区域

用户使用 git add . 将文件实际上就是添加到了缓存区。

用户使用 git commit 就是将文件从暂存区放置到分支上。

远程仓库入门

最初就已经写过 Git 是一个分布式版本控制系统,因此 分布式 才是 Git 最大的优势。

GitHub 是世界上最大的最大的 Git 托管网站,先申请账号,然后按照 点击跳转 配置 SSH 秘钥即可连接使用。

与 GitHub 类似,不过 GitLab 提供社区部,可以搭建私有 Git 服务器,对项目安全性有要求的个人或者企业推荐使用此产品。

创建远程仓库

若想在本地修改然后推送到远程仓库保存,并且可以在 电脑A 、电脑B等上进行修改然后推送,需要创建一个远程仓库。

本文以 GitHub 为例,GitLab 同理。

打开 GitHub 首页,然后点击绿色按钮 New Repository 。

带红星的空为必填项,需要填写仓库名称。下面的 Description 为仓库描述,选填项。

创建完毕后,GitHub 上会出现新手指导。

将文件添加到远程仓库

查看远程仓库信息

$ git remote -v

注意:只有正确配置了远程仓库且有访问权限的才能看到详细的远程仓库信息。

添加文件到远程仓库有两种方式

$ git remote add origin git@github.com:Vndroid/example.git

注意:Git 的默认远程仓库名为 origingithub.com: 后的请用自己的 GitHub 地址替换掉,其他人因为秘钥不同是无法推送至他人的仓库的。

$ git push -u origin master

注意:-u 会将本地的 master 分支与远程 master 分支关联。

$ git push origin master

再次推送时只需简化命令即可。

$ git clone git@github.com:Vndroid/example.git

注意:克隆有两种方式,SSH 和 HTTP(S) 请根据自己的网络状态进行判断使用哪种方式,部分公司内限制只能只能 HTTP 方式连接,但是每次需要确认身份进行验证才能推送。

$ git add .
$ git commit -m "First time"

先将文件添加至文件夹内,然后将更改修改至本地仓库,最后再推送至远程仓库。

$ git push -u origin master

分支管理

分支就是创建一个项目单独的时间线,类似于平行宇宙,互相之间有联系,但是没影响。

实际用途就是开发新的功能或者大的变动时,比如新功能很宏大,需要很长时间开发,那么其中每次的修改都是不完整的,会导致产品功能出现问题,影响此项目的其他人的使用及合并,若等待全部开发完成后再推送又存在因意外原因丢失进度的问题,因此创建一个单独的分支在其中进行修改,开发完成后合并进入主分支即可。

创建分支

每次提交 (commit)都会产生一次“进度点”,多个“进度点”就组成了一个“时间线”,这条线就是一条分支,在 Git 中默认的分支就是主分支(master),而 Git 会用 HEAD 来标记当前处于哪个分支中。

然后新建一个开发(dev)分支

$ git branch dev

切换分支

$ git checkout dev

输出结果

Switched to branch 'dev'

实际上以上两步可以使用一个命令进行实现,日常也推荐使用以下命令

$ git checkout -b dev

小贴士:-b 参数表示创建并切换至分支。

切换分支后实际上只是将 HEAD 指针指向了其他位置,但是实际上工作区的文件内容是一致的。

切换分支后继续提交只是将 dev 分支推进了一步,而 master 分支在原地不动。

查看分支

若想查看当前位于哪个分支上

$ git branch

输出结果

* dev
  master

注意:前面的星号标记为当前的分支。

合并分支

可以看到实际上“时间线”还是一条,因为没有在旧分支上继续修改,此时若想合并分支,实际上就是将 dev 上的修改合并至 master 。

$ git merge dev

注意:合并分支时,需要先切换到待合并分支上,然后进行合并,比如想把 dev 分支合并到 master,需要先切换到 master 进行合并。

可以看到合并后再删除分支会导致分支信息丢失,可以使用 No-Fast-Forward 模式进行合并,合并时会创建一个 commit 来标记分支合并过程。

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

合并冲突

部分情况下,会遇到当前分支与合并分支之间存在冲突,即修改的内容存在不一致的情况,此种情况下需要手动进行修复冲突后进行合并,Git 会提示存在冲突的文件及位置。

Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
$ git status -s
$ git add README.md
$ git commit

可以看到冲突的分支会变成另一条单独的“时间线”,解决冲突并合并后会重新变为一条“时间线”。

删除分支

$ git branch -d dev

提示:-d 可以理解为 --delete 为删除分支。

输出结果

Deleted branch dev (was f172g07).

暂停分支

很多时候正在进行功能的开发,但是突发情况,比如线上版本发现了 bug,需要速度修复,但是手中的功能还没开发完毕无法合并到 dev 分支。

那么此时就需要将现在的时间冻结(暂停进度)切换到新的分支修复问题,注意此功能仅仅保存于本地,不能跨机器存储!

$ git stash

输出结果

Saved working directory and index state WIP on dev: 74d22fv add merge

实际使用中推荐使用

$ git stash save "Description"

注意:使用此命令会给当前“快照”增加一个说明内容,以便恢复时识别,特别针对于已经存在快照,多个快照共存的情况下。

然后查看分支状态

$ git status

就会发现是干净的工作区,已经将工作进度保留,此时可以切换到新的分支来修复问题了,在新分支修复后合并,然后删除临时分支即可。

查看全部的快照列表

$ git stash list

输出结果

stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size"
stash@{2}: WIP on master: 21d80a5 added number to log

现在需要恢复之前的工作状态

$ git stash pop

注意:此命令会恢复之前的状态并删除缓存堆栈中的这个临时快照。

$ git stash apply [stash@{0}]

注意:此命令不会删除缓存堆栈中的临时快照。[] 内为可选项,默认恢复至最近的快照,即 stash@{0} ,也可单独指定为某个快照。

删除快照

$ git stash drop stash@{0}

注意:{} 内数值替换为想删除的快照ID即可。

废弃分支

很多新功能将在新建的临时分支中进行开发,但有时候因各种原因导致项目停止或者废弃,开发中止,而此分支还没有进行合并,必须彻底销毁。

$ git branch -D temp-branch

注意:未合并的分支用 -d 参数无法进行删除,只能使用 -D 进行删除。

分支策略

日常项目中主分支(master)当做稳定发布版本,仅用来发布新版本,不用来工作。

每个参与开发者都建立自己的 dev 分支,然后定期合并到主分支即可。

最终效果如下图

标签管理

既然已经存在 commit id 为什么还要制作一个 标签(tag)系统,原因很简单,因此 commit id 的版本号很复杂,为了便于人们理解,可以给某个 commit 打 标签,比如 标记 commit u53d762 版本为 v1.0 ,那么对于这个就很好理解。

$ git tag v1.0

注意:默认会把标签打在当前分支的当前 commit 上,因此需要打标签时,需要先切换到指定分支上。

若之前的版本忘记打标签,需要补标签。

$ git tag v0.9 f52c633

注意:只需要知道打标签的 commit ID 即可,可以在 log 中查看id。

回滚版本

很多时候会遇到一种情况,提交某次改动后发现还有应该优化的小改动,此时不应该再作为一个单独的版本进行提交,所以应进行回滚,修改后和上次的改动一起提交。

完成此操作需要先获取 Git 历史,找到想回滚到的版本号。

$ git log

然后回滚本地版本库。

$ git reset --soft a226e4f97e5538d13b51e6cffd1ef9c882ab54a0

小贴士:--soft 不会将工作区的文件进行回滚,--hard 则会将工作区文件也进行回滚,请根据需要进行调整。

撤销成功后可以核对提交历史,然后重新提交。

$ git push origin master --force

小贴士:必须使用 --force 参数进行强制提交,否则会报错。

然后修改文件后重新提交即可。

附录

参考链接

回复