关于git的使用介绍网上也比较多了,尤其是廖雪峰大佬的git教程与苏玲大佬的git网课都是git入门的经典。写这篇文章主要是想通过图示化配合文字的方式,加深记忆,也在日常工作中起到速查的作用。
git简介
Git 是一个免费的开源 分布式版本控制系统,旨在快速高效地处理从小到大的所有项目。这是git官网对其的描述。
在没有出现版本控制系统前,常见的作法是在一台服务器上把文件共享出来,每个成员各自创个文件夹,以目录拷贝的方式来区别不同的成员开发的东西以及不同的版本,缺点显而易见,公共文件容易被覆盖,代码集成效率低下。慢慢的产生了cvs和svn之类的集中式版本管理系统,其通过一个版本管理服务器来记录每个文件和文件夹的版本演变历史,也有分支管理的功能,集成效率有了明显的提高,但是版本库都集中存放在了中央服务器,干活时需要先从中央服务器拉取最新的版本,然后在本地开发,最后推送到服务器,没网是寸步难行,性能和效率是最大的问题。后来linux创始人linus写出了分布式的版本控制系统——git,其去中心化的思想,让每个客户端都相关于是一个完整的版本库,加上增强性的分支管理能力,使性能和效率有了很大提升,目前已经成为了市场上主流的版本控制系统,伴随着git的日趋成熟,也出现了很多基于git的web端产品,比如github和gitlab。
git安装
不同操作系统的git安装方式可以参考官方文档
git安装
git核心原理
版本管理实现.git
git相比svn等工具具备最优的存储能力,也可以实现在没有远端中心服务器的情况下,在本地具备版本管理能力。
当我们使用git init
后会在当前目录生成一个.git
的隐藏目录,此目录下的各种文件就是git核心的一些东西。
1 | [root@slions_pc1 first_git]# git init |
.git/config文件
顾名思义,config文件存储的是当前仓库的配置信息,如果什么也没有配置时,它长这样:
1 | [root@slions_pc1 first_git]# cat .git/config |
修改此文件的配置会立即生效,当然推荐做法是使用命令git config --local
来配置。
在多人协助时,一定要配置user.name
与user.email
,这样才能区别用户。
如下示例,我配置当前仓库的用户与邮箱,可以看到配置完.git/config
也更新了信息。
1 | [root@slions_pc1 first_git]# git config --global user.name slions |
后续我们在此仓库中提交的用户就都是slions
了。
使用git config --global
可以配置全局的git配置,也就是说如果本地仓库没有设置专属配置时,就会以全局配置作为默认配置,相应的全局也有config文件,linux的在/root/.gitconfig
,windows在用户宿主目录下的~/.gitconfig
。
.git/objects目录
存储是版本控制系统中非常重要的技术点,特别是当各种文档频繁的进行变更与回退,如果没有一个好的存储系统作为支撑,那版本库会随着日积月累越来越大,性能和效率都会变差。
git使用SHA-1的Hash算法来记录与存储文件,不管输入数据的数据量有多大,使用同一个哈希算法,得到的加密结果长度固定;哈希算法确定,输入数据确定,输出结果保证不变。
git是一个内容寻址文件系统,通过hash算法得到文件的“指纹”(40位16进制数字),通过文件指纹存取数据,存取的数据都在objects目录。
object目录下有3种类型的数据:
- Commit
- Tree
- Blob
在使用git的过程中,创建的提交会被存储为Commit
类型的文件,文件夹会被存储为Tree
类型的文件,文件会被存储为Blob
类型的文件。其中pack目录会存储的是一些打包文件(Git 往磁盘保存对象时默认使用的格式叫松散对象 (loose object) 格式,Git 时不时地将这些对象打包至一个叫 packfile 的二进制文件以节省空间并提高效率)。
示例说明:
此时我们位于默认的master分支,工作区与暂存区都是干净的,创建一个hello.file文件,查看.git/objects
无任何变化,因为hello.file还没被git纳管。
1 | [root@slions_pc1 first_git]# git status |
添加hello.file到暂存区,可以看到git/objects中多了一个目录与文件,拼起来正是hello.file的hash值,使用git cat-file -p
查看文件的内容就是hello.file的内容,使用git cat-file -t
查看其类型为blob。
1 | [root@slions_pc1 first_git]# git add . |
创建一个文件夹试试。
1 | [root@slions_pc1 first_git]# mkdir mydir |
提交看看效果。
1 | [root@slions_pc1 first_git]# tree .git/ |
将hello.file与mydir/test.file提交后,发现在objects下多了3个文件,分别是af/5f8664975db42eb0780392064254516a5386ae
,c6/98f655405d32c8d5130f3cd5393dfa5897275d
,e9/a3da963bf7298300b2130a64315799b6baef31
查看其内容与类型。
1 | [root@slions_pc1 first_git]# git cat-file -p af5f8664975db42eb0780392064254516a5386ae |
可以发现,objects中commit类型的文件内容与git log
的内容如出一辙,且commit ID一致。
1 | [root@slions_pc1 first_git]# git log |
再创建个提交来观察下变化。
1 | [root@slions_pc1 first_git]# echo test2 > mydir/test2.file |
查看多出来的这4个文件的内容与类型。
1 | [root@slions_pc1 first_git]# git cat-file -p 180cf8328022becee9aaa2577a8f84ea2b9f3827 |
这次可以看到48acad07a1446a569f8fe2ea06ef15973291561b
的内容多了一个parent行,其“指纹”和第一次提交时的一致。
通过一张图来说明当前我本地环境中commit、tree、blob之间的关系。
如果我们创建相同的100个文件,并且分别进行提交操作,因为文件内容都一样,所以object中只会存一份blob,会出现100个tree,大大的提高了存储空间的利用率于寻址效率。
当前提交两个commit后git仓库可简化为下图:
.git/refs目录
refs目录存储的都是引用文件。如本地分支,远端分支,标签等。
- refs/heads/xxx 本地分支
- refs/remotes/origin/xxx 远端分支
- refs/tags/xxx 本地tag
引用文件的内容都是40位长度的commit
1 | [root@slions_pc1 first_git]# tree .git/refs/ |
当前git仓库逻辑拓扑为下图:
.git/HEAD文件
HEAD目录存储的是当前所在的位置,内容是分支的名称:
1 | [root@slions_pc1 first_git]# cat .git/HEAD |
当前git仓库逻辑拓扑为下图:
.git/branches文件
1 | [root@slions_pc1 first_git]# git branch -v |
git常用命令速查
新建代码库
把已有的项目代码纳入git管理
1 | cd 项目代码所在的文件夹 |
新建的项目直接用git管理
1 | cd 某个文件夹 |
下载一个项目和它的整个代码历史
1 | git clone [url] |
配置
显示当前的Git配置
1 | git config --list |
编辑Git配置文件
1 | git config -e [--global] |
设置提交代码时的用户信息
1 | git config [--global/local] user.name "[name]" |
增加删除文件
添加指定文件到暂存区
1 | git add [file1] [file2] ... |
添加指定目录到暂存区,包括子目录
1 | git add [dir] |
添加当前目录的所有文件到暂存区
1 | git add . |
删除工作区文件,并且将这次删除放入暂存区
1 | git rm [file1] [file2] ... |
停止追踪指定文件,但该文件会保留在工作区
1 | git rm --cached [file] |
文件重命名
改名文件,并且将这个改名放入暂存区
1 | git mv [file-original] [file-renamed] |
代码提交
提交暂存区到仓库区
1 | git commit -m [message] |
提交暂存区的指定文件到仓库区
1 | git commit [file1] [file2] ... -m [message] |
提交工作区自上次commit之后的变化,直接到仓库区
1 | git commit -a |
提交时显示所有diff信息
1 | git commit -v |
使用一次新的commit,替代上一次提交,如果代码没有任何新变化,则用来改写上一次commit的提交信息
1 | git commit --amend -m [message] |
重做上一次commit,并包括指定文件的新变化
1 | git commit --amend [file1] [file2] ... |
分支
列出所有本地分支
1 | git branch |
列出所有远程分支
1 | git branch -r |
列出所有本地分支和远程分支
1 | git branch -a |
新建一个分支,但依然停留在当前分支
1 | git branch [branch-name] |
新建一个分支,并切换到该分支
1 | git checkout -b [branch] |
新建一个分支,指向指定commit
1 | git branch [branch] [commit] |
新建一个分支,与指定的远程分支建立追踪关系
1 | git branch --track [branch] [remote-branch] |
切换到指定分支,并更新工作区
1 | git checkout [branch-name] |
切换到上一个分支
1 | git checkout - |
建立追踪关系,在现有分支与指定的远程分支之间
1 | git branch --set-upstream [branch] [remote-branch] |
合并指定分支到当前分支
1 | git merge [branch] |
选择一个commit,合并进当前分支
1 | git cherry-pick [commit] |
删除分支
1 | git branch -d [branch-name] |
删除远程分支
1 | git push origin --delete [branch-name] |
标签
列出所有tag
1 | git tag |
新建一个tag在当前commit
1 | git tag [tag] |
新建一个tag在指定commit
1 | git tag [tag] [commit] |
删除本地tag
1 | git tag -d [tag] |
删除远程tag
1 | git push origin :refs/tags/[tagName] |
查看tag信息
1 | git show [tag] |
提交指定tag
1 | git push [remote] [tag] |
提交所有tag
1 | git push [remote] --tags |
新建一个分支,指向某个tag
1 | git checkout -b [branch] [tag] |
查看信息
显示有变更的文件
1 | git status |
显示当前分支的版本历史
1 | git log |
显示commit历史,以及每次commit发生变更的文件
1 | git log --stat |
搜索提交历史,根据关键词
1 | git log -S [keyword] |
显示某个commit之后的所有变动,每个commit占据一行
1 | git log [tag] HEAD --pretty=format:%s |
显示某个commit之后的所有变动,其”提交说明”必须符合搜索条件
1 | git log [tag] HEAD --grep feature |
显示某个文件的版本历史,包括文件改名
1 | git log --follow [file] |
显示指定文件相关的每一次diff
1 | git log -p [file] |
显示过去5次提交
1 | git log -5 --pretty --oneline |
显示所有提交过的用户,按提交次数排序
1 | git shortlog -sn |
显示指定文件是什么人在什么时间修改过
1 | git blame [file] |
显示今天你写了多少行代码
1 | git diff --shortstat "@{0 day ago}" |
显示某次提交的元数据和内容变化
1 | git show [commit] |
显示某次提交发生变化的文件
1 | git show --name-only [commit] |
显示某次提交时,某个文件的内容
1 | git show [commit]:[filename] |
显示当前分支的最近几次提交
1 | git reflog |
从本地master拉取代码更新当前分支:branch 一般为master
1 | git rebase [branch] |
对比
显示暂存区和工作区的代码差异
1 | git diff |
显示暂存区和上一个commit的差异
1 | git diff --cached [file] |
显示工作区与当前分支最新commit之间的差异
1 | git diff HEAD |
显示两次提交之间的差异
1 | git diff [first-branch]...[second-branch] |
远程同步
更新远程仓储
1 | git remote update |
下载远程仓库的所有变动
1 | git fetch [remote] |
显示所有远程仓库
1 | git remote -v |
显示某个远程仓库的信息
1 | git remote show [remote] |
增加一个新的远程仓库,并命名
1 | git remote add [shortname] [url] |
取回远程仓库的变化,并与本地分支合并
1 | git pull [remote] [branch] |
上传本地指定分支到远程仓库
1 | git push [remote] [branch] |
强行推送当前分支到远程仓库,即使有冲突
1 | git push [remote] --force |
推送所有分支到远程仓库
1 | git push [remote] --all |
撤销
恢复暂存区的指定文件到工作区
1 | git checkout [file] |
恢复某个commit的指定文件到暂存区和工作区
1 | git checkout [commit] [file] |
恢复暂存区的所有文件到工作区
1 | git checkout . |
重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
1 | git reset [file] |
重置暂存区与工作区,与上一次commit保持一致
1 | git reset --hard |
重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
1 | git reset [commit] |
重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
1 | git reset --hard [commit] |
重置当前HEAD为指定commit,但保持暂存区和工作区不变
1 | git reset --keep [commit] |
新建一个commit,用来撤销指定commit
后者的所有变化都将被前者抵消,并且应用到当前分支
1 | git revert [commit] |
暂时将未提交的变化移除,稍后再移入
1 | git stash |
其他
生成一个可供发布的压缩包
1 | git archive |
多彩log输出
1 | git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit" |
推荐文章
git动图展示工作流: https://zhuanlan.zhihu.com/p/132573100
使用原理视角看git: https://www.cnblogs.com/Coding-net/p/5577485.html