作者的操作系统环境是Ubuntu。
一、简介
Git由Linux内核开发者Linus使用C语言开发创建,是一个分布式版本控制系统工具。
二、安装
在Ubuntu上安装Git的命令如下:
1 | sudo apt-get install git |
三、起点
首先需创建一个Git的本地仓库,有两种方式:
- 在某个目录下执行
git init
命令,初始化创建得到一个Git的本地仓库 - 执行
git clone 远程仓库地址
命令,克隆远程仓库创建得到一个Git的本地仓库
作者采用第1种方式,具体是在“dslztx”目录下执行git init
命令,此时“dslztx”目录便是一个Git的本地仓库,在“dslztx”目录下执行ls -A
命令,打印结果如下,即目录下存在一个名为“.git”的目录:
1 | dsl@ubuntu:~/tmp2/dslztx$ ls -A |
四、基础操作
4.1、说明
本地仓库由两部分构成:“本地工作区域”和“.git目录区域”。“.git目录区域”是本地仓库的核心数据区域,尽量不要进行手动更改。“.git目录区域”内包含有“配置文件,数据文件,提交节点拓扑图,暂存区”等内容,其中对我们理解Git工作原理比较重要的是“暂存区”和“提交节点拓扑图”。
4.1.1、本地工作区域
本地仓库下除“.git目录区域”以外的区域,在这里对文件进行直接操作。
4.1.2、暂存区
通过Git命令将在“本地工作区域”中的操作记录保存到“暂存区”,在执行git commit
命令时,保存在“暂存区”中的操作记录会被最终提交。
4.1.3、提交节点拓扑图
每次成功执行git commit
命令都会创建一个相应的“提交节点”,每个提交节点都包含有“提交节点ID,提交用户,提交用户邮箱,日期,提交说明”等内容,每个“提交节点”实质上对应一个完整的版本快照,因此“提交节点ID”可作为版本号。
每个“提交节点”还包含有另外一个重要的字段内容:一个“来源提交节点指针列表”,指针列表中的指针指向本提交节点的“来源提交节点”。每个提交节点的“来源提交节点指针列表”的大小大于等于1(除了“根提交节点”,它的指针列表的大小等于0,即没有“来源提交节点”),即每个“提交节点”至少有一个“来源提交节点”。因此,在一系列提交之后,最终可获得一个“提交节点拓扑图”。
“提交节点ID”由40个十六进制字符构成,为了便于操作,Git支持使用“提交节点ID”的可区别(可与已经存在的其他“提交节点ID”进行区别)的前缀子序列来指代该“提交节点”。比如有一个“提交节点”,它的“提交节点ID”为“46400e7d7bffaf8b9921bd4d48d3286364809cc2”,可使用“46400e”来指代该“提交节点”(假设“46400e”是可区别的前缀子序列)。
4.2、实战
4.2.1、提交
1、命令和说明git commit
:最终提交保存在暂存区中的操作记录,如上所述,每次成功提交都会创建一个相应的提交节点。
2、实战
参见下面小节。
4.2.2、增
1、命令和说明git add FILE_NAME
:如果是新增文件,则将文件的新增操作记录保存到暂存区;如果是非新增文件,则将文件的修改操作记录保存到暂存区。
2、实战
新建文件“readme.md”,先执行git add readme.md
命令,再执行git commit . -m 'add'
命令,最后执行git log
命令,打印最近提交信息结果如下,可知本次提交的“提交节点ID”为“46400e7d7bffaf8b9921bd4d48d3286364809cc2”:
1 | commit 46400e7d7bffaf8b9921bd4d48d3286364809cc2 |
执行git log --graph --topo-order --decorate --oneline --all
命令,查看此时的提交节点拓扑图,结果如下:
1 | * 46400e7 (HEAD, master) add |
4.2.3、改——修改内容
1、命令和说明
修改内容没有特定的Git命令,使用VIM等文本编辑器即可,但是修改好后需要执行git add FILE_NAME
命令,将文件的修改操作记录保存到暂存区。
2、实战
修改“readme.md”文件,修改后内容如下:
1 | 床前明月光 |
执行git add readme.md
命令,再执行git commit . -m 'alter'
命令,最后执行git log
命令,打印最近提交信息结果如下,可知本次提交的“提交节点ID”为“4c4c21a00b0d75acf853e5f7de1cd1c8fe87c0e4”:
1 | commit 4c4c21a00b0d75acf853e5f7de1cd1c8fe87c0e4 |
执行git log --graph --topo-order --decorate --oneline --all
命令,查看此时的提交节点拓扑图,结果如下:
1 | * 4c4c21a (HEAD, master) alter |
4.2.4、改——重命名
1、命令和说明git mv FILE_NAME NEW_FILE_NAME
:将名为“FILE_NAME”的文件重命名为“NEW_FILE_NAME”,将文件的修改操作记录保存到暂存区。
备注:git mv FILE_NAME NEW_FILE_NAME
实质上就是git rm FILE_NAME
和git add NEW_FILE_NAME
的命令组合。在执行git mv FILE_NAME NEW_FILE_NAME
命令后,分别独立执行git reset HEAD FILE_NAME
和git reset HEAD NEW_FILE_NAME
命令得到两个不同的结果,可以借此加以辅证。
2、实战
先执行git mv readme.md rename_readme.md
命令,再执行git commit . -m 'rename'
命令,最后执行git log
命令,打印最近提交信息结果如下,可知本次提交的“提交节点ID”为“ba32a2c15af27a44080e813a8b37dc143dab7eb2”:
1 | commit ba32a2c15af27a44080e813a8b37dc143dab7eb2 |
执行git log --graph --topo-order --decorate --oneline --all
命令,查看此时的提交节点拓扑图,结果如下:
1 | * ba32a2c (HEAD, master) rename |
4.2.5、撤销——未保存到暂存区
1、命令和说明git checkout -- FILE_NAME
:对文件进行了修改,但修改操作记录还未保存到暂存区,执行该命令可撤销修改。
2、实战
修改“rename_readme.md”文件,修改后内容如下:
1 | 床前明月光 |
执行git checkout -- rename_readme.md
命令,撤销修改。
4.2.6、撤销——已保存到暂存区
1、命令和说明
依次执行git reset HEAD FILE_NAME(其实质就是“特定文件版本跳转”,详见“4.2.9、特定文件版本跳转”)
和git checkout -- FILE_NAME
:对文件进行了修改,且修改操作记录已保存到暂存区,执行该组合命令可撤销修改。
2、实战
修改“rename_readme.md”文件,修改后内容如下,再执行git add rename_readme.md
命令将修改操作记录保存到暂存区:
1 | 床前明月光 |
依次执行git reset HEAD rename_readme.md
和git checkout -- rename_readme.md
命令,撤销修改。
4.2.7、删
1、命令和说明git rm FILE_NAME
:删除文件,将删除操作记录保存到暂存区。
2、实战
先执行git rm rename_readme.md
命令,再执行git commit . -m 'delete'
命令,最后执行git log
命令,打印最近提交信息结果如下,可知本次提交的“提交节点ID”为“5fb58d0a15f1dc3d1fe1963832b7ddb3f774f036”:
1 | commit 5fb58d0a15f1dc3d1fe1963832b7ddb3f774f036 |
执行git log --graph --topo-order --decorate --oneline --all
命令,查看此时的提交节点拓扑图,结果如下:
1 | * 5fb58d0 (HEAD, master) delete |
4.2.8、版本跳转
1、命令和说明
可跳转到“过去版本”,也可在处于“过去版本”时跳转到“过去的将来已存在版本(当然不能是未存在的版本)”,即可在已存在版本间进行任意跳转。
版本跳转有两种方式:绝对跳转和相对跳转。个人推荐使用绝对跳转。
- 绝对跳转:显式指定需要跳转到版本的版本号,由上述描述可知,即对应的“提交节点ID”,具体命令为
git reset 提交节点ID
- 相对跳转:使用“HEAD”标号,比如“git reset HEAD
N”形式,它表示跳转到当前版本的前第N个版本,另外还可使用“git reset HEAD^N”形式,“HEAD^N”等价于“HEADN”,比如“HEAD^”等价于“HEAD1”,“HEAD^^”等价于“HEAD2”。
备注:
当处于“过去版本”时欲再跳转到“过去的将来已存在版本”时,有可能出现不能获取到“欲跳转到版本的版本号”的情形,此时可尝试使用git reflog
命令,即通过Git命令历史记录尝试获取“欲跳转到版本的版本号”。
2、实战
经过上述一系列操作,本地仓库下除了“.git”目录外没有任何文件。执行git log --graph --topo-order --decorate --oneline --all
命令,查看此时的提交节点拓扑图,结果如下:
1 | * 5fb58d0 (HEAD, master) delete |
执行git reset 46400e7
命令,跳转到版本“46400e7d7bffaf8b9921bd4d48d3286364809cc2”,此时有文件“readme.md”,且该文件的内容为空;接着执行git reset ba32a2c
命令,跳转到版本“ba32a2c15af27a44080e813a8b37dc143dab7eb2”,此时文件“readme.md”有内容“床前明月光”,且已被重命名为“rename_readme.md”;继续执行git rm rename_readme.md
命令,最后执行git commit . -m 'delete again'
命令进行提交。
此时执行git log
命令,打印最近提交信息结果如下,可知本次提交的“提交节点ID”为“45c6a0062a302375ae5e1f8f7e9e47b798f74f76”:
1 | commit 45c6a0062a302375ae5e1f8f7e9e47b798f74f76 |
执行git log --graph --topo-order --decorate --oneline --all
命令,查看此时的提交节点拓扑图,结果如下:
1 | * 45c6a00 (HEAD, master) delete again |
最后执行git reset 5fb58d0
命令,进行版本回跳,执行git log --graph --topo-order --decorate --oneline --all
命令,查看此时的提交节点拓扑图,结果如下:
1 | * 5fb58d0 (HEAD, master) delete |
4.2.9、特定文件版本跳转
1、命令和说明
“4.2.8、版本跳转”小节介绍了整体版本跳转,有时候我们也需要“特定文件的版本跳转”,两者使用的命令几乎一致,只不过后者增加了一个“文件名参数”,因此命令形式为git reset 提交节点ID FILE_NAME
。
2、实战
增加文件“1.txt”和“2.txt”,内容分别如下:
1 | 昔人已乘黄鹤去,此地空余黄鹤楼 |
1 | 渭城朝雨浥轻尘 |
执行git add .
命令,再执行git commit . 'add again'
命令,最后执行git log
命令,打印最近提交信息结果如下,可知本次提交的“提交节点ID”为“d0c01f40c5205e69b4487e4581cb94f9c27fd79e”:
1 | commit d0c01f40c5205e69b4487e4581cb94f9c27fd79e |
修改文件“1.txt”和“2.txt”,修改后内容分别如下:
1 | 昔人已乘黄鹤去,此地空余黄鹤楼 |
1 | 渭城朝雨浥轻尘 |
执行git add .
命令,再执行git commit . 'alter again'
命令,最后执行git log
命令,打印最近提交信息结果如下,可知本次提交的“提交节点ID”为“7a9965c9fc2f328aeaf6434c93c2634fde8317e8”:
1 | commit 7a9965c9fc2f328aeaf6434c93c2634fde8317e8 |
执行git log --graph --topo-order --decorate --oneline --all
命令,查看此时的提交节点拓扑图,结果如下:
1 | * 7a9965c (HEAD, master) alter again |
执行git reset d0c01f4 1.txt
命令,跳转到版本“d0c01f40c5205e69b4487e4581cb94f9c27fd79e”,此时文件“1.txt”的内容为昔人已乘黄鹤去,此地空余黄鹤楼
,文件“2.txt”的内容保持不变。
五、进阶——分支
5.1、说明
在Git中,具有“分支”的概念,从提交节点拓扑图的角度来看,从分支当前最新提交节点回溯到根提交节点的路径就是分支的完整内容。每条分支都具有一个相应的“分支名称变量”,该分支名称变量是一个指针变量,它指向该分支的当前最新提交节点。另外还有一个已在上面提及的“HEAD变量”,它也是一个指针变量,它始终指向当前分支的分支名称变量。
默认具有一条名为“master”的分支,上述描述操作都基于该条分支。
综上,具有多条分支的提交节点拓扑图示意图如图1所示。
图1
一些分支相关操作的实现机理描述如下:
- 整体版本跳转。当前分支的分支名称变量指向欲跳转到版本对应的提交节点
- 特定文件的版本跳转。当前分支的分支名称变量指向的提交节点保持不变
- 查看分支。查看所有分支以及当前分支。
- 创建分支。创建一个与新建分支名称对应的分支名称变量,它的初始值与创建分支时当前分支的分支名称变量值一致,即指向创建分支时当前分支的最新提交节点。在切换到该新建分支并进行一系列提交之后,该新建分支的分支名称变量会指向该新建分支中的最新提交节点
- 切换分支。切换分支时,HEAD变量指向欲切换到分支的分支名称变量,直接修改工作区的内容(关于这点,注意跟“整体版本跳转”和“特定文件的版本跳转”中的相关行为进行区分)
- 合并分支。在合并分支时,合并两个分支的内容,直接修改工作区的内容(关于这点,注意跟“整体版本跳转”和“特定文件的版本跳转”中的相关行为进行区分),并且可能自动提交创建一个最新的提交节点
- 删除分支。直接删除对应分支的分支名称变量,其他保持不变
5.2、实战
5.2.1、查看分支
1、命令和说明git branch
:查看所有分支以及当前分支(名称前有“*”符号的即为当前分支)。
2、实战
参见下面小节。
5.2.2、创建分支
1、命令和说明git branch BRANCH_NAME
:创建名为“BRANCH_NAME”的新分支。
2、实战
执行git branch dev
命令创建新分支dev,再执行git branch
命令查看分支,结果如下(master分支为当前分支):
1 | dev |
执行git log --graph --topo-order --decorate --oneline --all
命令,查看此时的提交节点拓扑图,结果如下:
1 | * 7a9965c (HEAD, master, dev) alter again |
5.2.3、切换分支
1、命令和说明git checkout BRANCH_NAME
:切换到名为“BRANCH_NAME”的分支。
2、实战
执行git checkout dev
命令切换到dev分支,再执行git branch
命令查看分支,结果如下(dev分支为当前分支):
1 | * dev |
执行git log --graph --topo-order --decorate --oneline --all
命令,查看此时的提交节点拓扑图,结果如下:
1 | * 7a9965c (HEAD, master, dev) alter again |
5.2.4、合并分支
1、命令和说明git merge BRANCH_NAME
:合并名为“BRANCH_NAME”的分支到当前分支,如果合并有冲突需首先解决冲突。
2、实战
在master分支下,添加文件“3.txt”,并提交到Git本地仓库,执行git log
命令,打印最近提交信息结果如下,可知本次提交的“提交节点ID”为“5c28268b825ec3d00f5908929b96cca99fe724b0”:
1 | commit 5c28268b825ec3d00f5908929b96cca99fe724b0 |
在dev分支下,添加文件“4.txt”,并提交到Git本地仓库,执行git log
命令,打印最近提交信息结果如下,可知本次提交的“提交节点ID”为“6252efa4705464efccb4213580a7376811c954e8”:
1 | commit 6252efa4705464efccb4213580a7376811c954e8 |
执行git log --graph --topo-order --decorate --oneline --all
命令,查看此时的提交节点拓扑图,结果如下:
1 | * 6252efa (HEAD, dev) 4.txt |
切换到master分支,执行git merge dev
命令合并dev分支到当前master分支。再执行git log --graph --topo-order --decorate --oneline --all
命令,查看此时的提交节点拓扑图,结果如下(合并时做了一次自动提交):
1 | * 5ba2e1a (HEAD, master) Merge branch 'dev' |
5.2.5、删除分支
1、命令和说明git branch -d BRANCH_NAME
:删除名为“BRANCH_NAME”的分支。
2、实战
执行git branch -d dev
命令删除dev分支,再执行git branch
命令查看分支,结果如下:
1 | * master |
执行git log --graph --topo-order --decorate --oneline --all
命令,查看此时的提交节点拓扑图,结果如下:
1 | * 5ba2e1a (HEAD, master) Merge branch 'dev' |
六、进阶——远程仓库
6.1、说明
远程仓库的作用:既可作为本地仓库的远程备份,又可作为多人协作的中间联络者。接下来以流行的Github上的远程仓库为例进行介绍。
6.2、实战
6.2.1、关联本地仓库和远程仓库
1、命令和说明
如果通过执行git clone 远程仓库地址
命令方式,克隆远程仓库而创建得到本地仓库,那么该本地仓库已自动与远程仓库建立关联;如果通过在目录下执行git init
命令而初始化创建得到本地仓库,那么后续可执行git remote add SHORT_NAME 远程仓库地址(SHORT_NAME表示远程仓库别名,便于后续引用该远程仓库,默认的别名为origin)
命令建立该本地仓库与远程仓库的关联。
2、实战
在Github上新建一个远程仓库(假设远程仓库地址为“git@github.com:dslztx/learngit.git”),Git本地仓库使用已存在的“dslztx”本地仓库,将两者进行关联的命令为git remote add origin git@github.com:dslztx/learngit.git
。
6.2.2、推送到远程仓库
1、命令和说明git push origin master
:将本地仓库的master分支的内容推送到远程仓库的master分支,如果有冲突,先解决冲突,再推送。
2、实战
在dslztx本地仓库下执行git push origin master
命令,可发现“git@github.com:dslztx/learngit.git”指代的远程仓库下有了一个master分支,且该分支的内容与本地仓库的master分支内容一致。
6.2.3、从远程仓库拉取
1、命令和说明git pull origin master
:从远程仓库的master分支拉取内容到本地仓库的master分支。
2、实战
在dslztx本地仓库下执行git pull origin master
命令,可发现本地仓库master分支的内容与“git@github.com:dslztx/learngit.git”所指代远程仓库下master分支内容一致。
七、进阶——标签管理
7.1、说明
标签的作用:作为提交节点的别名,相较于提交节点ID,更加便于记忆和使用。
7.2、实战
7.2.1、查看标签
1、命令和说明git tag
:查看所有标签,标签默认按照标签名称的字典序进行排序。
2、实战
参见下面小节。
7.2.2、查看标签对应版本信息
1、命令和说明git show TAG_NAME
:查看指定标签对应的版本的信息。
2、实战
参见下面小节。
7.2.3、创建标签
1、命令和说明git tag TAG_NAME [COMMIT_ID]
:给指定的提交节点添加名为“TAG_NAME”的标签,如果未指定提交节点,则默认给当前最新的提交节点添加标签。
2、实战
执行git tag "v1.0" 5ba2e1
命令给“5ba2e1ab2756fdf2c1ed9f36cc01b2deb20d8b9b”提交节点添加标签“v1.0”,再分别执行git tag
和git show "v1.0"
命令,得到结果分别如下:
1 | v1.0 |
1 | commit 5ba2e1ab2756fdf2c1ed9f36cc01b2deb20d8b9b |
7.2.4、推送标签到远程仓库
1、命令和说明git push origin TAG_NAME
或者git push origin --tags
:推送指定标签或者所有标签到远程仓库。
2、实战
执行git push origin "v1.0"
命令,将“v1.0”标签推送到远程仓库。
7.2.5、删除标签
1、命令和说明git tag -d TAG_NAME
:删除名为“TAG_NAME”的标签。
2、实战
执行git tag -d "v1.0"
命令,删除“v1.0”标签。
7.2.6、删除远程仓库标签
1、命令和说明
先在本地执行git tag -d TAG_NAME
命令删除“TAG_NAME”标签,然后再执行git push origin :refs/tags/TAG_NAME
命令将删除“TAG_NAME”标签操作推送到远程仓库。
2、实战
在“7.2.5、删除标签”小节中已删除本地“v1.0”标签的前提下,再执行git push origin :refs/tags/v1.0
命令删除远程仓库中的“v1.0”标签。
八、其他
8.1、配置管理
执行git commit
命令时需要指定“提交用户”和“提交用户邮箱”(即提交信息中“Author:”字段的两部分内容),可使用git config
命令进行配置,根据配置的作用域不同,可主要分为两种方式:用户级作用域和仓库级作用域。
1、用户级作用域
示例如下:
1 | git config --global user.name "dslztx" |
2、仓库级作用域
示例如下:
1 | git config --local user.name "dslztx" |
8.2、文件过滤
Git允许使用名为“.gitignore”的特殊文件,在该特殊文件内可编写用于描述需要被过滤文件的表达式。一些常用的文件过滤表达式可参见链接。
8.3、命令缩写
Git提供命令缩写机制,具体命令配置形式为git config alias.SHORT_NAME FULL_NAME
,在配置后,执行git SHORT_NAME
即等价于执行git FULL_NAME
。根据配置的作用域不同,可主要分为两种方式:用户级作用域和仓库级作用域。
1、用户级作用域
示例如下:
1 | git config --global alias.st status |
2、仓库级作用域
示例如下:
1 | git config --local alias.st status |
九、博文定位
本博文的定位是作为Git快速入门的教程,因此,一些深入的细节并未探讨和涉及,比如下面这些内容:
- 一些Git命令被实现得越来越智能,使得有些前导步骤可被省略。比如
git commit
命令:欲删除已在Git仓库中的文件,本来应依次执行git rm
和git commit
命令,现在依次执行rm
和git commit
命令也能达到等价的效果;欲提交对已存在Git仓库中的文件的修改,本来应依次执行git add
和git commit
命令,现在直接执行git commit
命令即可达到等价的效果 - 对于本地仓库与远程仓库之间的内容推送和拉取,本文只讨论了默认单分支的简单情形,而没有考虑多分支等复杂情形
- 几个基础简单的命令并未进行讲解,默认读者都已经掌握,比如“git diff”,“git status”等命令
参考文献: [1]http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000 [2]https://gist.github.com/datagrok/4221767