Git&GitHub就是这么简单
Posted 穆瑾轩
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Git&GitHub就是这么简单相关的知识,希望对你有一定的参考价值。
Git&GitHub就是这么简单
1、GitHub的历史
可能大家都听过Git和GitHub,关于他们也有很多的描述,要说清楚他们之间的关系,故事还得从那一年说起。
时间 | 描述 |
1970-80年初 | 最初美国贝尔实验室的Ken Thompson,以BCPL语言为基础,设计出很简单接近硬件的B语言,并用B语言写了第一个UNIX操作系统;早期程序员可以用手工的方式进行备份,并以注释或者新建文本文件来记录变动,如使用cp 命令备份,使用tar命令将一些文件归为一个.tar文件。 后来Walter F. Tichy使用C开发了RCS (Revision Control System)用于版本控制,RCS允许多个用户同时读取文件,但只允许一个用户锁定(locking)并写入文件 (类似于多线程的mutex)。RCS的互斥写入机制避免了多人同时修改同一个文件的可能,但代价是程序员长时间的等待,给团队合作带来不便。(RCS本地版本控制系统) |
1986 | 直到1986年,Dick Grune写了一系列的shell脚本用于版本管理,并最终以这些脚本为基础,构成了CVS (Concurrent Versions System)版本控制系统,CVS后来用C语言重写,CVS是开源软件,CVS被包含在GNU的软件包中,并因此得到广泛的推广,CVS继承了RCS的集中管理的理念,CVS引进了分支(branch)的概念,分支是主干文件在本地复制的副本,用户对本地副本进行修改,且可以在分支提交(commit)多次修改。用户在分支的工作结束之后,需要将分支合并到主干中,以便让其他人看到自己的改动。CVS也有许多被人诟病的地方,如两个用户同时合并,那么合并结果将是某种错乱的混合体。 后来Karl Fogel和Jim Blandy(是长期的CVS用户)开发了Subversion,依赖类似于硬连接(hard link)的方式来提高效率,避免过多的复制文件本身。 |
1991-2001 | Linux 之父 Linus Tovalds 在 1991 年创建开源的 Linux 操作系统之后,起初参与Linux开源项目的代码是由Linus Torvalds本人通过“diff”和“patch”命令来手动为别人整合代码的。Linux开源的内核项目管理起来一直很麻烦,因为项目实在是太大了,开发者花费过多的时间在版本管理上,社区的弟兄们也对这种方式表达了强烈不满。 |
2002 | 到了2002年,一个叫Tim Kemp 的人发现 Subversion 是一个非常好的版本管理系统,但是缺乏一个好的图形界面客户端程序。做一个与 Windows 外壳整合的 Subversion 客户端程序的想法是受一个叫 TortoiseCVS 的 CVS 客户端程序所启发的。Tim 研究了 TortoiseCVS 的源码并以此为 TortoiseSVN 的基础,这也就是我们现在使用的SVN(是一种集中式的版本控制系统)。 Linus Torvald本人相当厌恶CVS以及Subversion,于是Linus选择了一个商业的版本控制系统BitKeeper(分布式VCS)BitKeeper的东家BitMover公司出于人道主义精神,授权Linux社区免费使用这个版本控制系统,但前提是Linux社区的用户不去破解BitKeeper。 |
2005 | 一晃眼,到了2005年,Linux社区牛人聚集,由于社区开发Samba的Andrew好奇地破解了BitKeeper公司的分布式VCS产品然后被对方发现,导致Linux项目被BitMover公司回收了免费使用权。Linus向BitMover公司道个歉,于是Linus最终决定写一款开源的分布式VCS软件,花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!一个月之内,Linux系统的源码已经由Git管理了!(git分布式版本控制系统)(分布式,当我们连接共享版本库时,可以先将服务器上的项目,克隆到本地,相当于每一台电脑上都有整个项目的文件备份,在没有网时也可以开发,完成开发后,可以先提交到本地仓库,当有网的时候,再提交到共享版本库,这样一来,如果我们的服务器或者我们自己的电脑出故障,我们也没有任何担心的) |
2008 | GitHub(Git中心枢纽),于2007年10月1日开始开发,由GitHub公司,的开发者Chris Wanstrath、PJ Hyett和Tom Preston-Werner使用Ruby on Rails编写而成,他的UI设计确实有点糟糕。网站于2008年2月以beta版本开始上线,4月份正式上线。2008年7月,发布了Gists功能,用于托管代码片段。2008年12月,发布了GitHub Pages功能,这样大家就可以基于这个的repo,创建网站了。 |
2011 | 到了2011年,GitHub公司启动GitHub Enterprise项目,探索盈利模式。也是在11月,Github拥有了100万用户。 |
2014 | 2014年5月,Atom编辑器免费开源。现在大家常用的VSCode就是基于Atom。 |
2018 | 2018年6月,微软宣布收购GitHub,耗资75亿美元。GitHub上已经有了3000万开发者。 |
2019 | 1月份,GitHub宣布私有仓库全部免费,无限创建,但是最多只能有三个合作者。因为GitHub上性别严重失衡,男性群体高达95%以上,所以GitHub经常被大家戏称为GayHub,也是全球最大同性交友网站。 |
总结:Git 是由 Linux 之父 Linus Tovalds 为了更好地管理linux内核开发而创立的分布式版本控制系统。GitHub 的核心是一个名为 Git 的开源版本控制系统,Git 负责在您的计算机上本地发生的所有与 GitHub 相关的事情,GitHub可以托管各种git库,并提供一个web界面。GitHub的独特卖点在于从另外一个项目进行分支的简易性。为一个项目贡献代码非常简单,首先点击项目站点的“fork”按钮,然后将代码检出并将修改加入到刚才分出的代码库中,最后通过内建的“pull request”机制向项目负责人申请代码合并。
不要觉得这个有多难,理清思路,一步一步来,所有问题都可以迎刃而解。
2、Git的安装及简介
2.1、git相关素语
在了解git的使用是,我们需要先了解一下几个相关的素语,就能更好的理解git了。git的大致流程如下图所示:
2.1.1、工作区(Working Directory)
简单的说,工作区就是一个目录,一个项目存放的地方(本地导出或新建项目的地方)。
2.1.2、暂存区(Stage)
暂存区(stage),数据(快照)暂时存放的地方,git add就是将工作区的修改缓存在暂存区。git commit 时就是一次性把暂存区所有修改提交到仓库(分支)。
2.1.3、Repository-仓库(存储库)
我们可以把暂存区的内容提交到我们的(本地)仓库,也叫版本库(Respository)该目录下的所有文件都会被 git 管理起来,每个文件的修改、删除、git 都能跟踪,以便随时追踪历史,和还原。
.git
隐藏目录就是 git 的版本库,里面存了很多东西,最重要的就是 stage(index) 暂存区,还有第一个分支 master,以及指向 master 的 HEAD 指针。
注:HEAD 是一个指针,总是指向当前分支。
2.1.4、远程仓库(remote)
远程仓库其实就是指托管在因特网或其他网络中(一台服务器)的你的项目的版本库(当然也可以有好几个远程仓库)。每个人都可以从这个远程仓库克隆一份到自己的电脑上,并且各自把各自的内容提交并推送到远程仓库里,也可以从远程仓库中拉取其他人提交的文件到本地。如:GitHub、Gitlab 等都属于远程仓库。
就好像我们购物一样,我们看到好几个商品,觉得有点意向,然后把他们添加(git add)到购物车(暂存区)里,在没提交订单前,购物车里的东西我们增增减减,最后我们终于选好了我们的商品,点击提交(commit)订单,我们提交了订单,但是还没支付,也就没有推送给商家(远程仓库)。看到花了不少钱,我们得确定下订单,于是我们找到未支付的订单列表(本地仓库),当然也能看到我们以前的一些订单,确认没问题了,我们准备支付推送(Push)给商家(远程仓库),商家就会收到这个订单。其实如果你用过SVN,对checkout和commit并不会陌生。
在了解GitHub前,我们先来了解一下git的基本使用。
2.2、Git的安装
我们去官网下载一个版本控制软件,地址https://git-scm.com/downloads。
后续步骤一直往下点就行,安装完成。后续步骤均在windows中完成。
2.3、创建/克隆仓库
2.3.1、git init
创建一个空的 Git 存储库或重新初始化一个现有的存储库。我们均可以使用git init --help 命令来查看帮助文档。
# 语法:
git init [-q | --quiet] [--bare] [--template=<template_directory>]
[--separate-git-dir <git dir>] [--object-format=<format>]
[-b <branch-name> | --initial-branch=<branch-name>]
[--shared[=<permissions>]] [directory]
#-------------------常用命令--------------------
# 在当前目录新建一个 Git 仓库
$ git init
# 新建一个目录,并将其初始化为 Git 仓库
$ git init [project-name]
# 为新创建的存储库中的初始分支使用指定的名称
$ git -b [branch-name]
2.3.2、git clone
将存储库克隆到新目录中
语法:
git clone [--template=<template_directory>]
[-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
[-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
[--dissociate] [--separate-git-dir <git 目录>]
[--depth <depth>] [--[no-]single-branch] [--no-tags]
[--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]
[--[no-]remote-submodules] [--jobs <n>] [--sparse] [--[no-]reject-shallow]
[--filter=<过滤器>] [--] <存储库>
[<目录>]
# 从远程下载一个仓库
$ git clone [url]
2.3.3、 创建仓库案例
使用git init,在当前目录新建一个仓库,当前目录下多了一个.git
的目录,这个目录是Git来跟踪管理版本库的。
2.4、Git目录说明
执行 git init会在当前目录生成.git,其中就包含如下这些文件/文件夹,为了更好的了解git,我们需要了解一下这些目录的作用。
2.4.1、hooks目录
Git 默认会在这个目录中放置一些示例脚本。 这些脚本除了本身可以被调用外,可以设置特定的git命令后触发相应的脚本。
钩子种类 | 描述 |
提交工作流钩子 |
|
电子邮件工作流钩子 | git am命令会调用: applypatch-msg、pre-applypatch、 post-applypatch这些钩子。 |
其它钩子 | pre-rebase;可以禁止对已经推送的提交;
post-checkout:
... |
2.4.2、info目录
存储库的其他信息将记录在此目录,我们可以看到目录下有个info/exclude文件,用来 忽略指定模式的文件,和 .gitignore 类似,但是 .gitignore 是针对每个目录的。
2.4.3、logs目录
保存所有更新的引用记录。其目录结构如下:
├── HEAD
└── refs
├── heads
│ ├── master
...
HEAD 记录所有更改记录,包括切换分支,logs/refs 下存储本地更改记录,如果有远程,也会记录远程remotes更改记录。
2.4.5、refs目录
目录存放了各个分支(包括各个远端和本地的HEAD)所指向的commit对象的指针(引用),也就是对应的sha-1值。
2.4.6、其他文件
- config:git配置信息,包括user.name,email,remote repository的地址,本地branch和remote branch的follow关系;(当前用户当前仓库的配置,在linux版本中,还有/etc/gitconfig,~/.gitconfig)
- HEAD:存放的是一个具体的路径,也就是refs文件夹下的某个具体分支。意义:指向当前的工作分支。项目中的HEAD 是指向当前 commit 的引用,它具有唯一性,每个仓库中只有一个 HEAD。在每次提交时它都会自动向前移动到最新 的 commit;
如: $ cat .git/HEAD ref: refs/heads/master
- index:存放的索引文件,可使用 git ls-files --stage 查看;
- description:用来显示对仓库的描述信息。
2.4.7、objects目录
Git对象是一个内容可寻址的文件系统,Git核心是一个简单的键值数据存储。可以将任何类型的内容插入到 Git 数据库中,Git 将为您返回一个唯一的键值,通过该键值可以在任意时刻再次检索该内容。
注:这里的key 即为 SHA1计算后的值。objects目录下存储有三种对象:数据对象(blob object)、树对象(tree object)、提交对象(commit object)。
进入.git\\objects目录,我们会发现有很多2个字符的目录、info 和 pack目录。
1)info 和 pack 目录
当存储的文件很大时,git 会进行压缩,会存储到 info 和 pack 下;
2)2个字符的目录
这两个字母是计算的SHA1值(总共40个字符)的前两个字符,剩余的38个是该目录下的文件名。
扩展:Git 对象数据库中的三种对象
他们是怎么存储的呢?我们需要了解一下git存储的原理,以便后面更好的理解并使用git的高级命令。
1、数据对象(blob object)
git使用了blob对象去存储了文件的内容。使用hash-object 命令将对象内容写入数据库,以此生成一个blob object。
# 创建一个新的数据对象并将它手动存入你的新 Git 数据库中
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
# 这些都是底层命令 实际上我们后面会用一个高级命令
# 命令参数选项
-w: 设置该参数指示hash-object 命令将对象内容写入数据库,若不指定此选项该命令仅返回对应的键值
–stdin: 表示从标准输入读取对象内容,若不指定此选项,则必须在尾部命令给出存入数据库的路径
–: 标记后续参数类型,即 – 后面的参数会被解析为file
# 如果你对linux命令属性 | 管道的应用就不会陌生,当然我们也可以这样做
#例如:
# 在工作区中新建一个文件
echo "test content" > testdc.txt
# 将文件中的内容,写入数据库(存文件)
git hash-object -w 文件路径
# 如果不加参数,不会写入数据库(返回对应文件的键值)
git hash-obejct 文件路径
上述命令的输出是一个 40 个字符的校验和哈希。这是 SHA-1 散列,改变内容则哈希值发送变化(和md5算法道理类似)。使用git命令查看数据库:
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ find .git/objects/ -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
在.git/objects目录下,我们可以看到多了一个文件,/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4,也就是说,Git存储内容的方式是使用SHA-1 散列的前2位做子目录,后38位作为存储内容的文件名。 我们使用cat-file 命令输出内容和对象类型:
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
$ git cat-file -t d670460b4b4aece5915caf5c68d12f560a9fe3e4
blob
#参数
-p 输出对象内容
-t 输出对象类型
有没有发现,当我们使用hash-object命令去git数据库中存储文件时,我们在仓库的目录(d/gitRepository)中找不到对应的文件。因为我们没给它一个文件名。只是将文件内容存储在.git/objects目录下。
2、树对象(tree object)
为了解决存储文件名的问题,于是有了树对象,允许我们将多个文件组织到一起。Git 以类似于 UNIX 文件系统的方式存储内容,所有内容都存储为树和 blob 对象,树对应于 UNIX 目录,而数据对象(blob)对应inode 或文件内容。单个树对象包含一个或多个条目,每个条目都是 blob 或子树的 SHA-1 哈希值及其关联的模式、类型和文件名。一个树对象也可以包含另外一个树对象。
官方的介绍都是比较精炼的,大致意思可以这么说,在实际项目过程中,我们每次提交都是一个多文件的提交。很少的时候是单文件的,那此时Git就不是单单存储一个 blob对象了,而是 tree对象。tree对象,见名知意,就是一个树对象,类似于操作系统目录,tree的分支,可能还是tree,也可能是blob。
2.1、构建树对象
要构建树对象,并塞入到暂存区,需要使用update-index、write-tree、read-tree等命令。
1)先创建一个test.txt的数据对象(blob 对象)
# 清空之前的版本库,初始化之后,我们在工作目录中新建一个文件
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ echo "test version1" > test.txt
# 使用hash-object 命令将对象内容写入数据库
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git hash-object -w ./test.txt
warning: LF will be replaced by CRLF in ./test.txt.
The file will have its original line endings in your working directory
fbb2ff04e2cae17293a83c5e83af505f62cd13d1
# 目前工作目录中只有一个文件
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ ls -l
total 1
-rw-r--r-- 1 Administrator 197121 14 Jul 21 21:57 test.txt
# 查看暂存区中的文件,现在是没有的
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git ls-files -s
2)使用update-index来创建test.txt文件的首个版本,并加入到暂存区,通过write-tree生成树对象,产生我们管理的项目的第一个版本。
# 将test.txt加入到暂存区中
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git update-index --add --cacheinfo 100164 fbb2ff04e2cae17293a83c5e83af505f62cd13d1 test.txt
--add 因为此文件并不在暂存区,首次需要-add
--cacheinfo 会从已存在的数据库(Object)中取得对应的内容并添加到索引中。
--文件模式:
--100644 表明这是一个普通文件
--100755 表示一个可执行文件
--120000 表示一个符号链接
# git ls-files -s查看暂存区中的文件
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git ls-files -s
100755 fbb2ff04e2cae17293a83c5e83af505f62cd13d1 0 test.txt
# find .git/objects/ -type f 查看数据库中的对象
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ find .git/objects/ -type f
.git/objects/fb/b2ff04e2cae17293a83c5e83af505f62cd13d1
# git write-tree生成树对象(给暂存区拍一张快照)
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git write-tree
59e4512814d504004c6a86b2f0792adf8b004a1f
# 再次查看数据库中的对象,发现多了一个(树对象)
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ find .git/objects/ -type f
.git/objects/59/e4512814d504004c6a86b2f0792adf8b004a1f
.git/objects/fb/b2ff04e2cae17293a83c5e83af505f62cd13d1
# 查看对象的类型
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git cat-file -t 59e4512814d504004c6a86b2f0792adf8b004a1f
tree
详细解析:当我们把文件添加到暂存区后,然后使用write-tree生成树对象(给暂存区拍一张快照),并存储到数据库中,这时数据库中的这个树对象,其实就是我们管理的项目的第一个版本了。
快照和我们平时说的备份,有什么区别呢?备份:从一个地方复制到另一个地方,两个地方的数据都是完整独立的,而快照:更像文件系统的一种存储标记,比如,我们有一个文件a(1,2,3)--将其内容修改a(1,4,3),并生成一个快照A,快照A的存储仅仅是修改过程中数据的变化部分,即记录(数据位置2-变成了-4)而不是像备份复制那样某个路径下完整的数据。
3)新增new.txt 将new.txt和test.txt的第二个版本加入到暂存区,并生成树对象。
# 工作目录中新建一个new.txt
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ echo "new v1" > new.txt
# 将new.txt加入到数据库中(生成blob 对象)
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git hash-object -w new.txt
warning: LF will be replaced by CRLF in new.txt.
The file will have its original line endings in your working directory
eae614245cc5faa121ed130b4eba7f9afbcc7cd9
# 查看此时数据库中的对象
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ find .git/objects/ -type f
.git/objects/59/e4512814d504004c6a86b2f0792adf8b004a1f
.git/objects/ea/e614245cc5faa121ed130b4eba7f9afbcc7cd9
.git/objects/fb/b2ff04e2cae17293a83c5e83af505f62cd13d1
# 查看修改前的test.txt文件内容
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ cat test.txt
test version1
# 修改test.txt的文件内容
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ vim test.txt
# 将修改的test.txt文件加入到数据库中(生成blob 对象)
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git hash-object -w test.txt
warning: LF will be replaced by CRLF in test.txt.
The file will have its original line endings in your working directory
3a31d973aae0a644183e2e72395e683973f4acc1
# 查看数据库中的对象
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ find .git/objects/ -type f
.git/objects/3a/31d973aae0a644183e2e72395e683973f4acc1
.git/objects/59/e4512814d504004c6a86b2f0792adf8b004a1f
.git/objects/ea/e614245cc5faa121ed130b4eba7f9afbcc7cd9
.git/objects/fb/b2ff04e2cae17293a83c5e83af505f62cd13d1
# 将test.txt加入的暂存区,此时暂存区中的test.txt已经更新了(加入到暂存区一定要指定文件,加入文件名写错了,也会覆盖)
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git update-index --cacheinfo 100644 3a31d973aae0a644183e2e72395e683973f4acc1 test.txt
# 将new.txt加入到暂存区
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git update-index --add --cacheinfo 100644 eae614245cc5faa121ed130b4eba7f9afbcc7cd9 new.txt
# 查看暂存区,当前暂存区和工作目录中的内容一致
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git ls-files -s
100644 eae614245cc5faa121ed130b4eba7f9afbcc7cd9 0 new.txt
100644 3a31d973aae0a644183e2e72395e683973f4acc1 0 test.txt
# 生成树对象(拍第二个快照,也就是我们项目的第二个版本)
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git write-tree
434388dae0ea7dd251c040ff1735d5a59454133c
# 查看数据库中的对象
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ find .git/objects/ -type f
.git/objects/3a/31d973aae0a644183e2e72395e683973f4acc1
.git/objects/43/4388dae0ea7dd251c040ff1735d5a59454133c
.git/objects/59/e4512814d504004c6a86b2f0792adf8b004a1f
.git/objects/ea/e614245cc5faa121ed130b4eba7f9afbcc7cd9
.git/objects/fb/b2ff04e2cae17293a83c5e83af505f62cd13d1
详细解析:我们往数据库中新增了一个数据对象,并修改了test.txt,也将其加入到数据库中,次数数据库中的对象有4个,分别是:
.git/objects/3a/31d973aae0a644183e2e72395e683973f4acc1 test.txt的第二版本
.git/objects/59/e4512814d504004c6a86b2f0792adf8b004a1f 树对象,即项目的第一个版本
.git/objects/ea/e614245cc5faa121ed130b4eba7f9afbcc7cd9 new.txt的第一个版本
.git/objects/fb/b2ff04e2cae17293a83c5e83af505f62cd13d1 test.txt的第一个版本
当我们把修改后的test.txt和new.txt加入到暂存区后,此时工作目录和暂存区中的内容是一致的,使用git write-tree命令生成树对象,我们的数据库中就多了一个树对象
.git/objects/43/4388dae0ea7dd251c040ff1735d5a59454133c 树对象,即项目的第二个版本
4)read-tree命令的使用
若需要对某个存在三级文件夹的二级文件夹进行write-tree操作, 在把三级文件夹下的所有修改文件生成blob后,进行整体tree对象化,之后再与二级文件夹同级的文件夹和文件进行相同操作。此时就需要用到: read-tree 命令。但是实际操作中我们并不会这么去手动操作,将一棵树接到另一颗树上。
# 该操作会把tree对象59e4512814d504004c6a86b2f0792adf8b004a1f 加入暂存区中,并取名bak(实际Git会把此prefix默认为文件夹的名字)
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git read-tree --prefix=bak 59e4512814d504004c6a86b2f0792adf8b004a1f
# 查看暂存区中的内容
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git ls-files -s
100755 fbb2ff04e2cae17293a83c5e83af505f62cd13d1 0 bak/test.txt
100644 eae614245cc5faa121ed130b4eba7f9afbcc7cd9 0 new.txt
100644 3a31d973aae0a644183e2e72395e683973f4acc1 0 test.txt
# 生成新的树对象
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git write-tree
9de7f27122d384812680907c0052ab66454c3fb4
第三棵树:9de7f27122d384812680907c0052ab66454c3fb4(项目的第三个版本)
最终就形成了如图所示效果:
项目的第三个版本并没有修改文件,只是将第一棵树加入到暂存区(索引),并生成了一颗新的树。现在呢,我们已经有了三个树对象,分别代表了我们要跟踪的项目的不同快照,如果想重用这些快照,就必须记住这三个哈希值,并且我们也不知道是谁保存了这些快照,什么时候保存的,以及为什么保存这些快照,于是就有了提交对象(commit object)。
3、提交对象(commit object)
提交对象能为你保存基本信息,我们可以通过调用commit-tree命令创建额提交对象,为此需要指定一个树对象的哈希值,以及该提交对象的父提交对象(第一次提交,则没有父对象)。
1)为第一棵树创建一个提交对象
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository/.git (GIT_DIR!)
$ echo 'first commit' | git commit-tree 59e4512814d504004c6a86b2f0792adf8b004a1f
384c65d38b4b57e074fd5bb8e1602516a2ac6b62
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository/.git (GIT_DIR!)
$ git cat-file -t 384c65d38b4b57e074fd5bb8e1602516a2ac6b62
commit
# 查看提交对象的信息,包含了第一棵树及作者、提交者(config中指定的user.name、user.email信息)及提交说明,提交对象是对树对象的一次封装
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository/.git (GIT_DIR!)
$ git cat-file -p 384c65d38b4b57e074fd5bb8e1602516a2ac6b62
tree 59e4512814d504004c6a86b2f0792adf8b004a1f
author mjx <871140750@qq.com> 1626965281 +0800
committer mjx <871140750@qq.com> 1626965281 +0800
first commit
2)为第二棵树创建一个提交对象
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository/.git (GIT_DIR!)
$ echo 'second commit' | git commit-tree 434388 -p 384c65
a78a51fff0535891210554d1bdcb8f0f3962fc11
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository/.git (GIT_DIR!)
$ git cat-file -p a78a51fff0535891210554d1bdcb8f0f3962fc11
tree 434388dae0ea7dd251c040ff1735d5a59454133c
parent 384c65d38b4b57e074fd5bb8e1602516a2ac6b62
author mjx <871140750@qq.com> 1626965733 +0800
committer mjx <871140750@qq.com> 1626965733 +0800
second commit
于是就能形成如官网所说的这种效果:
总结:项目的一个版本是一个提交对象,本质上项目的一个快照是一个树对象。
1)生成blob对象:git hash-object -w 文件路径
2)加入暂存区(更新索引):
git update-index --add 文件路径
git update-index --add --cacheinfo mode sha-1 文件名
git read-tree --prefix=test sha-1(某个tree的sha-1) 把某个tree读入索引中
3)创建树对象: git write-tree
4)创建commit对象: git commit-tree sha-1 -m "提交信息"
echo "提交信息" | git commit-tree sha-1 -p 父级sha-1
5)其他底层命令:
对象的内容查询:git cat-file -p sha-1
对象的类型查询:git cat-file -t sha-1
查看暂存区:git ls-files -s
以上就是git的底层原理,一般我们使用高级命令。
2.5、Git文件的四种状态
引用官方一张文件状态的生命周期图:
2.5.1、Untracked-未跟踪
此文件工作区中, 但并没有加入到git库, 不参与版本控制,通过git add
状态变为Staged。
工作目录下的文件要么是未跟踪,要么是已跟踪(已跟踪有三种状态:未修改、已修改、已暂存)
2.5.2、Unmodified-已经入库/未修改
版本库中的文件快照内容与工作区中完全一致,这种类型的文件可以是已经提交到版本库的文件或从远程仓库克隆到本地库而检出到工作区中的文件。如果它被修改, 而变为Modified,
如果使用git rm
移出版本库, 则成为Untracked
文件。
2.5.3、Modified-已修改
对工作区中的文件而言,文件已经修改了,但并没有进行其他操作。如果通过git add
可进入暂存staged
状态, 使用git checkout
则丢弃修改过, 返回到unmodified
状态, 这个git checkout
即从库中取出文件, 覆盖当前修改。
2.5.4、Staged-暂存状态
在工作区中的文件被添加到暂存区,在工作区中新增/修改的文件使用git add
可进入暂存staged
状态,执行git commit
则将修改同步到本地库中, 这时库中的文件和工作区的文件又变为一致, 文件为Unmodified
状态,执行git reset HEAD filename
取消暂存, 文件状态为Untracked/Modified.
3、Git高级命令使用
前面我们已经使用到了git init初始化了一个仓库,初始化后,在当前目录下会出现一个名为.git的目录,所有Git需要的数据和资源都存放在这个目录。
3.1、初始化配置
Git 的配置文件是 .gitconfig
,可以放在用户的主目录(全局配置)下或项目目录下(项目配置)。
# 显示当前的 Git 配置
$ git config --list
# 编辑 Git 配置
$ git config -e [--global]
# 设置用来提交代码的用户信息
$ git config [--global] user.name "[name]"
$ git config [--global] user.email "[email address]"
3.2、管理文件(增、删、改、查)
3.2.1、添加文件-git add-(增)
git add的作用是将文件/修改内容添加到暂存区。新增操作我们一般的执行步骤是:1)先执行git status 查看工作目录的文件状态;2)执行git add 添加文件;3)执行git commit -m 提交暂存区当前内容到仓库。
# 将指定文件添加到暂存区中
$ git add [file1] [file2] ...
# 将指定目录添加到暂存区中,包括子目录
$ git add [dir]
# 将当前目录中的所有文件添加到暂存区中
$ git add .
# 在添加每个更改之前都进行确认
# 对于同一个文件的多个更改,建议分开提交
$ git add -p
使用案例:
1)首先在git管理的目录下,新建两个文件(test.txt);
2)然后使用git add test.txt 将文件添加到暂存区(Stage),此时的文件状态为已暂存(staged);
3)使用git Gui工具可以看到,暂存区已经有一个文件了。(当然,我们也可以使用命令查看仓库的状态git status )
详细解析:
我们使用git add test.txt 文件相当于执行了:
git hash-object -w ./test.txt 生成了blob对象,并保存了相应的sha-1值
git update-index --cacheinfo sha-1 命令把 test.txt 放入了暂存区,并添加到了索引文件(index)中
注:git add 是先把内容放到了数据库中,在从数据库中取文件将其放入暂存区,并不是直接从工作目录到暂存区。
3.2.2、查看文件的状态-git status-(查)
git status查看工作目录中文件的状态(已跟踪(已提交、已暂存、已修改)、未跟踪)
# 以长格式输出输出,这是默认设置(可以不写后面的参数)。
$ git status --long
# 以短格式输出输出。
$ git status -s
使用案例:查看我们添加的文件test.txt
# 查看工作目录中文件的状态
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: test.txt
详细解析:
使用 git status 后,Git会对所有文件进行sha-1值计算,若计算到与.git/index索引中得对应文件的sha-1值不同了,则代表有所改动,则标记为 已修改(Modify),若发现索引中不存在对应文件的sha-1值, 则标记为未跟踪(Untracked )。
上图告诉我们一个新文件,test.txt还没被提交,同时也告诉我们可以使用it rm --cached <file>...命令将文件移出暂存区。
3.2.3、提交-git commit-(新增/删除/修改的提交)
git commit提交暂存区当前内容并添加描述信息(对仓库的更改)
# 将暂存区中的文件提交到代码仓库
$ git commit -m "commit message"
# 将指定的文件从暂存区中提交到仓库
$ git commit [file1] [file2] ... -m "commit message"
# 将工作区的更改直接提交到仓库
$ git commit -a
# 提交前展示所有的变动
$ git commit -v
# 使用新提交代替上次提交
# 如果代码没有任何变动,将会用于重写上次提交的提交信息
$ git commit --amend -m "commit message"
# 重做上次的提交,并将指定的文件包含其中
$ git commit --amend [file1] [file2] ...
使用案例:
# 提交暂存区的文件到仓库,如果注释比较少可以使用-m参数添加,如果注释较多,则不加参数会进入文件编辑模式
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git commit -m "第一次提交test.txt"
[master (root-commit) 2ca6bc4] 第一次提交test.txt
1 file changed, 1 insertion(+)
create mode 100644 test.txt
# 查看提交信息
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git log
commit 2ca6bc4bea79b316fed32ceb1a9bbbe8e0ec6efb (HEAD -> master)
Author: mjx <871140750@qq.com>
Date: Sat Jul 24 11:00:56 2021 +0800
第一次提交test.txt
# 此时工作目录和仓库是一致的
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git status
On branch master
nothing to commit, working tree clean
详细解析:
我们使用git commit -m "第一次提交test.txt" 文件相当于执行了:
git write-tree 生成了树(tree)对象,并产生了相应的树对象的sha-1值(保存项目的快照)
git commit-tree sha-1 -m 同时执行了git commit-tree对树对象的封装,包含提交注释、提交者的信息、时间,这是项目一个版本。
3.2.4、查看未暂存/未提交的修改-git diff-(查)
查看未暂存/未提交的修改。
# 显示暂存区和工作区的文件差别
$ git diff
# 显示暂存区和上一次提交的差别
$ git diff --cached [file]
# 显示工作区和当前分支的最近一次提交的差别
$ git diff HEAD
# 显示指定两次提交的差别
$ git diff [first-branch]...[second-branch]
# 显示今天提交了多少代码
$ git diff --shortstat "@{0 day ago}"
使用案例一:显示暂存区和工作区的文件差别
# 查看暂存区文件
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git ls-files -s
100644 915c628f360b2d8c3edbe1ac65cf575b69029b61 0 test.txt
# 编辑test.txt文件,新增一行内容test v2
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ vim test.txt
# 使用git diff 命令查看暂存区和工作目录的区别,
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git diff
warning: LF will be replaced by CRLF in test.txt.
The file will have its original line endings in your working directory
diff --git a/test.txt b/test.txt
index 915c628..95b7ed0 100644
--- a/test.txt
+++ b/test.txt
@@ -1 +1,2 @@
test v1
+test v2
详细解析:我们提交文件到仓库并不会清空暂存区的,所以当我们修改工作目录中的文件后,使用git diff 目录就能看到工作目录和暂存区中的区别。
使用案例一:显示暂存区和上一次提交的差别
# 将文件加入到暂存区
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git add test.txt
warning: LF will be replaced by CRLF in test.txt.
The file will have its original line endings in your working directory
# 查看暂存区和上一次提交的差别
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git diff --cached
diff --git a/test.txt b/test.txt
index 915c628..95b7ed0 100644
--- a/test.txt
+++ b/test.txt
@@ -1 +1,2 @@
test v1
+test v2
3.2.5、删除文件-git rm file-(删)
# 将指定文件从工作区删除,并将本次删除添加到暂存区
$ git rm [file1] [file2] ...
# 停止追踪指定的文件,不会删除文件
$ git rm --cached [file]
删除文件使用案例:
1)使用git rm test.txt 删除文件(工作区中的文件被删除),在暂存区有待提交的删除文件;
2)使用git commit删除(版本库)文件;
3)而使用 git rm --cached test_rmcached.txt 停止跟踪文件,在暂存区的文件被删除了,本地文件恢复Untracked状态。
# 删除文件
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git rm test.txt
rm 'test.txt'
# 工作目录被删除的文件待提交
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
deleted: test.txt
# 删除文件
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git commit -m "删除test.txt"
[master 74338f9] 删除test.txt
1 file changed, 1 deletion(-)
delete mode 100644 test.txt
# 新建一个文件
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ vim test_rmcached.txt
# 将文件加入都暂存区
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git add test_rmcached.txt
# 查看工作目录文件的状态
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: test_rmcached.txt
# 停止跟踪文件,但不删除文件
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git rm --cached test_rmcached.txt
rm 'test_rmcached.txt'
# 文件为未跟踪状态
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
test_rmcached.txt
详细解析:git rm 除了删除本地文件外也会将删除动作加入到暂存区;
3.2.6、重命名/移动文件-git mv <old> <new>(删)
# 移动/重命名文件
$ git mv <old> <new>
# 强制移动/重命名文件
$ git mv -f <old> <new>
使用案例:使用mv 命令将readme.txt 重命名为test.txt
# 新建readme.txt文件后提交到仓库,然后查看暂存区中的文件
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git ls-files -s
100644 504a7438c95afbd7f5280d756fb405bd85fbf19e 0 readme.txt
# 移动/重命名文件
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ mv readme.txt test.txt
# 查看工作目录中文件的状态
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: readme.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
test.txt
no changes added to commit (use "git add" and/or "git commit -a")
3.3、查看提交历史-git log
使用该命令后,去 .git/logs 下寻找当前分支对应的文件名,文件中的内容即为每一次提交的信息。
# 显示提交历史(它会列出所有历史记录,显示提交对象的哈希值,作者、提交日期、和提交说明)
$ git log
# 显示前n条提交历史
$ git log -n
# 只显示提交的 SHA1 值和提交信息
$ git log --oneline
# 显示两天前的提交历史
$ git log --since=2.days
# 指定作者
$ git log --author=mjx
# 指定关键字为“test”的所有提交
$ git log --grep=test
# 格式化输出模板
$ git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative
# 以列表方式查看指定文件的提交历史
$ git blame <file>
使用案例:
# 只显示提交的 SHA1 值和提交信息
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git log --oneline
c9b83dc (HEAD -> master) 移动删除文件
158f2d9 tjsc
f4b81ca tj readme
2ca6bc4 第一次提交test.txt
# 显示提交注释中包含tj的历史
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git log --grep="tj" --oneline
2261741 (HEAD -> master) tj
158f2d9 tjsc
f4b81ca tj readme
# 格式输出提交历史
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative
* 2261741 - (HEAD -> master) tj (8 minutes ago) <mjx>
* 4196d41 - 提交修改 (9 minutes ago) <mjx>
* cc5b6e6 - 提交修改后的文件 (11 minutes ago) <mjx>
* c9b83dc - 移动删除文件 (8 hours ago) <mjx>
* 158f2d9 - tjsc (8 hours ago) <mjx>
* f4b81ca - tj readme (8 hours ago) <mjx>
* 2ca6bc4 - 第一次提交test.txt (11 hours ago) <mjx>
# 以列表的形式显示提交记录
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git blame test.txt
cc5b6e6a (mjx 2021-07-24 21:45:52 +0800 1) readme v1
4196d419 (mjx 2021-07-24 21:47:38 +0800 2) test v2
22617412 (mjx 2021-07-24 21:49:28 +0800 3) blame 1
22617412 (mjx 2021-07-24 21:49:28 +0800 4) blame 1-1
3.4、分支
几乎所有的版本控制系统都以某种形式支持分支,使用分支意味着你可以把你的工作从开发主线上分离出来,以免影响开发主线,在很多的版本控制系统中需要创建一个源代码的副本。对于大项目来说,这样的过程耗费很多时间。而Git的分支模型及其的高效轻量。
在git上创建一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
3.4.1、查看分支列表-git branch
# 查看分支
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git branch
* master
详细解析:
每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支,使用git branch,输出的是master,它是一个主分支(默认)。当我们在提交的时候,指针也会随着提交往前走。
前面我们在介绍目录的时候,有一个叫HEAD的文件和一个refs的目录。
HEAD:存放的是一个具体的路径,也就是refs文件夹下的某个具体分支,意义:指向当前的工作分支。项目中的HEAD 是指向当前 commit 的引用,它具有唯一性,每个仓库中只有一个 HEAD。在每次提交时它都会自动向前移动到最新的commit;
refs的目录:目录存放了各个分支,所指向的commit对象的指针(引用),也就是对应的sha-1值。
当我们打开HEAD文件,里面的内容是ref: refs/heads/master,说明当前的分支是master主分支。
我们在进入refs/heads目录下,看到有个文件master,打开它,里面的有个哈希值(8707bd8b38058abbe339f03be91f4f0804a61369),查看后它是一个提交对象,也是最后一次的提交对象。
由此看来:分支就是一条提交对象串成的时间线,并且有一个活动的指针,指向当前最新的提交对象。
3.4.2、创建分支-git branch <branchName>
使用案例:git branch demo 命令创建一个demo分支
# 使用命令查看提交历史
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git log --oneline
8707bd8 (HEAD -> master) 第四次提交 mv readme文件
68c6120 第三次提交 readme.txt
ea4add4 修改test内容提交
3a003f5 第一次提交test
# 创建一个demo分支
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git branch demo
# 再次查看提交历史,我们发现(HEAD -> master, demo)里多了一个demo
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git log --oneline
8707bd8 (HEAD -> master, demo) 第四次提交 mv readme文件
68c6120 第三次提交 readme.txt
ea4add4 修改test内容提交
3a003f5 第一次提交test
创建好了一个demo分支,我们看到refs目录下也多出了一个文件,同时它里面也存在着和master同样的哈希值(8707bd8b38058abbe339f03be91f4f0804a61369),但是我们的HEAD文件中的内容依然是:ref: refs/heads/master,说明当前指针还在master主分支上。所以实际上分支,也是一个 commit 对象的引用。只是在GIT中专门有文件记录了分支名和指向。
当然还有git checkout -b /git switch -c 可以创建分支,这个更常用,我们甚至可以通过创建文件的方式,直接创建branch。
cd .git/refs/head/
echo '最新提交对象的哈希值' > 分支名
# 创建并切换到新分支
git switch -c dev
3.4.3、切换分支-git checkout <branch>
# 切换分支
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (master)
$ git checkout demo
Switched to branch 'demo'
D readme.txt
# (demo)切换成功
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (demo)
$
# 在demo分支上新建文件
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (demo)
$ echo "demo v1" > demo.txt
# 将demo.txt加入暂存区
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (demo)
$ git add ./
warning: LF will be replaced by CRLF in demo.txt.
The file will have its original line endings in your working directory
# 将demo.txt提交的仓库
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (demo)
$ git commit -m "我是demo分支的 demo.txt"
[demo 4acce70] 我是demo分支的 demo.txt
2 files changed, 1 insertion(+), 1 deletion(-)
create mode 100644 demo.txt
delete mode 100644 readme.txt
# 查看我的提交记录
Administrator@EA424S6AH1D5MQG MINGW64 /d/gitRepository (demo)
$ git log --oneline
4acce70 (HEAD -> demo) 我是demo分支的 demo.txt
8707bd8 (master) 第四次提交 mv readme文件
68c6120 第三次提交 readme.txt
ea4add4 修改test内容提交
3a003f5 第一次提交test
详细解析:
当使用 git checkout 的时候, Git内部实际上就是把当前的HEAD指针给指向了另一个分支,而实际上也就是把 .git/HEAD 文件内容修改为切换的分支,而 .git/HEAD 内容指向的就是 .git/refs/heads中的分支,此文件内容又是一个 commit 对象的 sha-1值,所以也就间接指向了某个具体的commit对象了, 从这个commit对象可得到它的父级对象,依次类推,即可得到完整的代码。
当前分支示意图:
3.4.4、强制删除分支-git branch -D <branch>
当我们的测试代码使用完成后,已经完成了他的使命,我们不需
以上是关于Git&GitHub就是这么简单的主要内容,如果未能解决你的问题,请参考以下文章