前言:
接触编程也快三年,竟然还不会Git,因此打算花上一段时间来好好学学Git和GitHub的使用。这篇博客是在学习Git过程中的一些笔记及个人的理解。希望能够帮到Git初学者。当然,文中可能也会存在一些错误,敬请指正。
这篇博客只是Git的基础部分,后续部分我会尽快整理完发布,敬请期待。关注✨✨博主
Git&GitHub(进阶)
什么是版本控制?
- 版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统
为什么要使用版本控制?
- 有了它你就可以将某个文件回溯到之前的状态,甚至将整个项目都回退到过去某个时间点的状态。
版本控制分为集中式和分布式两种:
(1)集中式(例:SVN)
集中化的版本控制系统诸如 CVS,SVN等,都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。
(2)分布式(例:Git)
客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来,因此Git每次存的都是项目的完整快照,需要的硬盘空间会相对大一点,但是Git团队对代码做了极致的压缩,所以最终需要的实际空间比SVN多不了太多,而且Git的回滚速度极快。
分布式的版本控制系统在管理项目时,存放的不是项目版本与版本之间的差异,它存的是索引(所需磁盘空间很少,所以每个客户端都可以放下整个项目的历史记录)
这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复 。
Git是目前世界上最先进的分布式版本控制系统。同生活中的许多伟大事件一样,Git诞生于一个极富纷争大举创新的年代。Linux内核开源项目有着为数众广的参与者。绝大多数的Linux内核维护工作都花在了提交补丁和保存归档的繁琐事务上(1991—2002年间)。到2002年,整个项目组开始启用分布式版本控制系统 BitKeeper来管理和维护代码。
到了2005年,开发 BitKeeper的商业公司同Linux内核开源社区的合作关系结束,他们收回了免费使用BitKeeper的权力。这就迫使Linux开源社区(特别是 Linux的缔造者Linus Torvalds )不得不吸取教训,只有开发一套属于自己的版本控制系统才不至于重蹈覆辙。他们对新的系统制订了若干目标。
自诞生于2005年以来,Git日臻成熟完善,在高度易用的同时,仍然保留着初期设定的目标。它的速度飞快,极其适合管理大项目,它还有着令人难以置信的非线性分支管理系统可以应付各种复杂的项目开发需求。
(1)Git安装
下载地址:git
下载完成之后,默认安装即可。
(2)Git的初始化配置
设置签名:
设置:user.name 和 user.email
作用:区分不同开发人员的身份。
1、项目级别 :仅在当前本地库范围内有效
信息保存位置:./.git/config 文件
git config user.name "Yang"
git config user.email "[email protected]"
2、系统用户级别:登录当前操作系统的用户范围
git config --global user.name "Yang"
git config --global user.email "[email protected]"
信息保存位置:~/.gitconfig 文件
3、优先级别
一般在新的系统上,我们都需要先配置下自己的 Git 工作环境。配置工作只需一次,以后升级时还会沿用现在的配置。当然,如果需要,你随时可以用相同的命令修改已有的配置。
查看当前Git的配置:
git config --list
删除当前配置:
git config --global --unset user.email
区域和对象
Git中有区域和对象两组概念
- 区域包括:工作区,暂存区,版本库
- 对象包括:git对象,树对象,提交对象
Git本质上是一个数据库
(1)区域
Git 的核心部分是一个简单的键值对数据库。 你可以向该数据库插入任意类型的内容,它会返回一个键值,通过该键值可以在任意时刻再次检索该内容
echo 'test content' | git hash-object -w --stdin
存储文件:
git hash-object -w 文件路径
返回对应文件的键值 :
git hash-object 文件路径
根据键值拉去数据:
git cat-file -p hash值
Git如何存储数据:
find .git/objects -type f
.git/objects/ad/bbc66d2ea5e38fe2f4a52622dccb8ea50e86ca
- 一个文件对应一条内容。 校验和的前两个字符用于命名子目录,余下的 38 个字符则用作文件名。
当前的操作都是在对本地数据库进行操作 不涉及暂存区
树对象(tree object),它能解决文件名保存的问题,也允许我们将多个文件组织到一起。 Git 以一种类似于 UNIX 文件系统的方式存储内容。 所有内容均以树对象和数据对象(git 对象)的形式存储,其中树对象对应了 UNIX 中的目录项,数据对象(git 对象)则大致上对应文件内容。 一个树对象包含了一条或多条记录(每条记录含有一个指向 git 对象或者子树对象的 SHA-1 指针, 以及相应的模式、类型、文件名信息)。 一个树对象也可以包含另一个树对象 。
理解:树对象就是暂存区的快照。
第一步:创建一个文件
echo "Test.txt v1" >test.txt
第二步:将这个文件写进数据库,即:生成Git对象。
git hash-object -w test.txt
第三步:为test.txt文件的首个版本生成一个暂存区
git update-index --add --cacheinfo 100644 85fd5815b7d6183c54e61f8f287488779fef55ed test.txt
第四步:生成暂存区快照:即,生成树对象并将该对象写进数据库。
git write-tree
git对象代表文件的一次次版本,树对象代表项目的一次次版本。
暂存区如果文件同名,就会进行覆盖。
验证操作如下:
# 初始化git仓库
git init
# 创建test.txt文件
echo "Test.txt v1" >test.txt
# 将 test.txt文件写进数据库,生成git对象
git hash-object -w test.txt
# 生成当前文件的暂存区
git update-index --add --cacheinfo 100644 85fd5815b7d6183c54e61f8f287488779fef55ed test.txt
# 将暂存区生成树对象,并写进数据库
git write-tree
# 编辑 test.txt 的第二个版本
vim test.txt
# 将 test.txt文件的第二个版本写进数据库,生成git对象
git hash-object -w test.txt
# 创建 new.txt 文件
echo "new file v1" > new.txt
# 将 new.txt 文件写进数据库,生成git对象
git hash-object -w new.txt
# 生成当前文件的暂存区
git update-index --add --cacheinfo 100644 b2b44573b56f7ea44cbe6e34a69b4f1b19311c5c new.txt
# 将暂存区生成树对象,并写进数据库
git write-tree
查看数据库中的git和树对象:
$ find .git/objects -type f
.git/objects/85/fd5815b7d6183c54e61f8f287488779fef55ed test.txt的第一个版本
.git/objects/93/fbb254f8820ecf2457427869315a2db2193668 项目的第一个版本(树对象)
.git/objects/b2/b44573b56f7ea44cbe6e34a69b4f1b19311c5c test.txt的第二个版本
.git/objects/5a/3f0a30cc507cf62ecaac79e752b7bfc8540405 new.txt的第一个版本
.git/objects/62/2621fdf7766bc839d1ff670b3eb6336cf2f775 项目的第二个版本(树对象)
查看当前暂存区状态:
$ git ls-files -s
100644 b2b44573b56f7ea44cbe6e34a69b4f1b19311c5c 0 new.txt
100644 85fd5815b7d6183c54e61f8f287488779fef55ed 0 test.txt
Git数据库当前的状态:
将第一颗树加入到第二颗树中:
git read-tree --prefix=bak 93fbb254f8820ecf2457427869315a2db2193668
查看当前暂存区状态:
$ git ls-files -s
100644 85fd5815b7d6183c54e61f8f287488779fef55ed 0 bak/test.txt
100644 b2b44573b56f7ea44cbe6e34a69b4f1b19311c5c 0 new.txt
100644 85fd5815b7d6183c54e61f8f287488779fef55ed 0 test.txt
查看当前数据库中的对象:
$ find .git/objects -type f
.git/objects/85/fd5815b7d6183c54e61f8f287488779fef55ed test.txt的第一个版本
.git/objects/93/fbb254f8820ecf2457427869315a2db2193668 项目的第一个版本(树对象)
.git/objects/b2/b44573b56f7ea44cbe6e34a69b4f1b19311c5c test.txt的第二个版本
.git/objects/5a/3f0a30cc507cf62ecaac79e752b7bfc8540405 new.txt的第一个版本
.git/objects/62/2621fdf7766bc839d1ff670b3eb6336cf2f775 项目的第二个版本(树对象)
.git/objects/fc/2aad7812a0f483f6dcd05d6fd4ea656291667c 项目当前根树
当前数据库对象联系结构如图:
理解树对象
Git 根据某一时刻暂存区(即 index 区域)所表示的状态创建并记录一个对应的树对象,如此重复便可依次记录(某个时间段内)一系列的树对象。其实树对象是对暂存区内操作的抽象,这颗树对象相对于就是快照。当我们的工作区有任何更改同步到暂存区时。便会调用 write-tree 命令通过 write-tree 命令向暂存区内容写入一个树对象。 它会根据当前暂存区状态自动创建一个新的树对象。即每一次同步都产生一颗树对象。且该命令会返回一个 hash 指向树对象。
我们可以认为树对象就是我们项目的快照
我们可以通过调用 commit-tree 命令创建一个提交对象,为此需要指定一个树对象的 SHA-1 值,以及该提交的父提交对象(如果有的话 第一次将暂存区做快照就没有父对象)
生成一个提交对象:
echo "frist commit" | git commit-tree 93fbb254f8820ecf2457427869315a2db2193668
查看生成的对象类型:
$ git cat-file -t 18f858702c9e42753d7065a10a8d6a684d068d3f
commit
查看生成的对象的内容:
$ git cat-file -p 18f858702c9e42753d7065a10a8d6a684d068d3f
tree 93fbb254f8820ecf2457427869315a2db2193668
author Yang <[email protected]> 1611220274 +0800
committer Yang <[email protected]> 1611220274 +0800
frist commit
注意:如果是已经有了一个提交对象,那么在创建当前提交对象时,就需要指定一个父对象。
echo 'second commit' | git commit-tree 树对象hash -p 父提交对象hash
当前数据库中对象的结构图:
git commit-tree 不但生成提交对象 而且会将对应的快照(树对象)提交到本地库中
总结:
1、git对象
git hash-object -w 文件路径
生成一个 key:value 键值对存储到 .git/objects 目录中
2、 tree对象
git update-index --add cacheinfo 100644 hash值 文件名
往暂存区添加一条记录 存储到 .git/index 目录中
git write-tree
生成树对象,存储到 .git/objects 目录中
3、commit对象
echo "My first commit" | git commit-tree 树的hash值
生成一个提交对象存储到 .git/objects 目录中
4、 对上述对象的查询
# 拿对应对象的内容
git cat-file -p hash值
# 拿对应对象的类型
git cat-file -t hash值
5、查看暂存区
git ls-files -s
clear :清除屏幕
echo ‘Hello World’:往控制台输出信息。也可以向一个文件中输出东西。代码如下:
echo "Hello World" > test.txt
ls(ll): 都是列出当前目录中的所有文件,只不过ll(两个ll)列出的内容更为详细。
find 目录名: 将对应目录下的子孙文件&子孙目录平铺在控制台
find 目录名 -type f :将对应目录下的文件平铺在控制台
touch : 新建一个文件 如 touch index.js
就会在当前目录下新建一个index.js文件。
pwd : 显示当前所在的目录路径。
history 查看命令历史。
rm: 删除一个文件, rm index.js
就会把index.js文件删除。
mkdir: 新建一个目录,就是新建一个文件夹。例:mkdir test
在当前目录下创建test文件夹
rm -r : 删除一个文件夹, rm -r test
删除test目录
mv 移动文件,mv index.html test
将目标文件index.html 移动到test文件夹中,当然, 这样写,必须保证文件和目标文件夹在同一目录下。
rm -rf /
:递归(f)删除所有文件,Linux中格式化系统
mv 源文件 重命名文件: 重命名
cat 文件的 url : 查看对应文件的内容。
vim 文件的 url(在英文模式下)
(2)初始化新仓库
在一个目录中打开 Git Bash Here,使用命令,将当前目录初始化成Git仓库。
git init
这个命令就是执行命令之后生成的目录,该目录就是我们生成的Git版本库。
hooks 目录包含客户端或服务端的钩子脚本;
info 包含一个全局性排除文件
logs 保存日志信息
objects 目录存储所有数据内容;相当于数据库
refs 目录存储指向数据的提交对象的指针(分支)
config 文件包含项目特有的配置选项
description 用来显示对仓库的描述信息
HEAD 文件指示目前被检出的分支
index 文件保存暂存区信息
git add .
先将工作目录中的修改,创建出一个git对象,放到版本库,再从版本库中将该git对象拿出来放到暂存区。(一个文件对应一个git对象)
执行完git add .
命令之后,git的版本库中有一个git对象。
执行完git commmit -m "这是我第一次提交"
之后,git的版本库中有三个对象。(一个git对象,一个树对象,一个提交对象)
git init
echo "这是我的第一个文件" > test.txt
$ git ls-files -s
# 这时,暂存区为空
find .git/objects -type f
# 版本库也为空
git add .
$ find .git/objects -type f
# .git/objects/ff/b2576c6fe91ca458e91df937da19dbb54da5be 版本库中有一个对象(git对象)
$ git ls-files -s
# 100644 ffb2576c6fe91ca458e91df937da19dbb54da5be 0 test.txt (暂存区也有一个对象)
git commit -m "这是我第一次提交"
$ git ls-files -s
# 100644 ffb2576c6fe91ca458e91df937da19dbb54da5be 0 test.txt (提交之后,不会覆盖暂存区)
$ find .git/objects -type f
# .git/objects/29/d1019b96640b9f85808986ffc52384c08fcfcb (提交之后,版本库中有三个对象)
# .git/objects/85/2fa69b14085db4ffb15923f07a5ccae6c7e09d
# .git/objects/ff/b2576c6fe91ca458e91df937da19dbb54da5be
总结:git操作基本流程
git add .
git commit -m "注释"
git add . 相当于执行了以下步骤:
git hash-object -w test.txt
git update-index --add --cacheinfo 100644 85fd5815b7d6183c54e61f8f287488779fef55ed test.txt
git commit -m “注释” 相当于执行了以下步骤:
git write-tree
git commit-tree
(1)Git高层基本命令
git init # 初始化仓库
git add . # 将修改添加到暂存区
git commit -m "注释" # 将暂存区提交到版本库
工作目录下的文件,只有两种状态:已跟踪、未跟踪
已跟踪的文件有三种状态:已提交、已修改、已暂存
(2)Git命令解析
# 查看暂存区状态
git status
# 当前做的哪些更新还没有暂存
git diff
# 有哪些更新已经暂存起来准备好了下次提交?
git diff –staged
创建一个新文件,它处于未跟踪状态
执行 git add .
之后,变为已跟踪且已暂存
执行提交之后
对 A 文件进行修改,查看暂存区状态
将修改后的文件跟踪,提交。
再次修改 A 文件,查看 git diff 和 git diff --staged。
git diff
当前做的哪些更新还没有暂存git diff --staged
有哪些更新已经暂存起来准备好了下次提交 (当前为空)执行 git add 之后,查看 git diff 和 git diff --staged。
提交修改,查看 git diff 和 git diff --staged
(3)git提交
1、可以进入vim编辑多行的注释信息,进行提交。
git commit
2、 跳过使用暂存区
Git 提供了一个跳过使用暂存区域的方式,只要在提交的时候,给 git commit 加上 -a 选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交, 从而跳过 git add 步骤
git commit -a -m "注释"
(3)删除操作
git rm 文件名
先从工作目录下删除这个文件,再执行 git add . ,我们只需再执行提交即可。
(4)重命名
git rm A.txt B.txt
相当于
$ mv A.txt aa.txt
$ git rm A.txt
$ git add aa.txt
(5)查看历史记录
git log
默认不用任何参数的话,git log 会按提交时间列出所有的更新,最近的更新排在最上面。 正如你所看到的,这个命令会列出每个提交的 SHA-1 校验和、 作者的名字和电子邮件地址、提交时间以及提交说明
多屏显示控制方式:
- 空格向下翻页
- b 向上翻页
- q 退出
git log --pretty=oneline
git log --oneline
# 查看完整日志信息,HEAD@{移动到当前版本需要多少步}
git reflog
项目的几个版本信息如下:
总结:
1、查看日志
# 查看工作目录中午文件的状态:未跟踪、已跟踪(已暂存、已提交、已修改)
git status
# 查看未暂存的修改
git diff
# 查看未提交的暂存
git diff --cache git diff --staged
# 查看提交记录
git log --oneline
# 查看整个项目的分支图
git log --oneline --decorate --graph --all
git diff 命令可以查看文件的修改
git diff HEAD 要和那个文件对比
# 和过去某个版本的文件进行比较
git diff HEAD^ 要笔记的文件名
使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线。
Git 的分支模型极其的高效轻量的。是Git 的必杀技特性,也正因为这一特性,使 得 Git 从众多版本控制系统中脱颖而出
分支的本质是一个指向提交对象的指针!
HEAD:
- 是一个指针,默认指向master分支,切换分支的本质就是让HEAD指向不同的分支指针。
- 每次提交新内容时,HEAD都会带着当前提交对象的指针往前移动
(1)创建分支并在新分支中工作
1、创建分支 (testing)
//git branch 分支名
git branch testing
2、切换分支 (切换到testing)
//git checkout 分支名
git checkout testing
3、在分支中提交
git commit -m "注释信息"
(2)分支相关命令解析
1、 创建 一个新分支,并不会自动切换到新分支中去
# 在当前提交对象上创建新的分支
git branch 新分支名
# 在指定的提交对象上创建新的分支
git branch 新分支名 提交对象
2、 查看所有分支信息
git branch
命令不只是可以创建与删除分支。 如果不加任何参数运行它,会得到当前所有分支的一个列表。
git branch
3、删除分支
不能自己删除自己,需先切换到别的分支,再删除该分支。
d:删除已经合并过的分支,如果没有合并,就不允许删除该分支
D:强制删除分支
git branch -d name
4、查看每一个分支的最后一次提交
git branch -v
给Git命令起别名:
当git命令过长,不好记时,就可以使用起别名的方法。
git config --global alias.logall "log --oneline --decorate --graph --all"
log --oneline --decorate --graph --all
该命令是查看git中所有提交的日志信息。(完整显示)
5、版本穿梭
新建一个分支并且使该分支指向对应的提交对象
git branch 分支名 要到的版本hash
6、 创建一个新分支并切换过去
git checkout -b 分支名
7、 注意:
分支切换会改变你工作目录中的文件,在切换分支时,一定要注意你工作目录里的文件会被改变。 如果是切换到一个较旧的分支,你的工作目录会恢复到该分支最后一次提交时的样子。 如果 Git 不能干净利落地完成这个任务,它将禁止切换分支。
小技巧:每次在切换分支前,提交一下当前分支
如果在切换分支时,没有提交当前分支中的文件,那么当前分支中未提交的文件就会出现在切换回的分支中。
坑:
在切换分支时,如果当前分支上有未暂存的修改(第一次) 或者 有未提交的暂存(第一次),分支可以切换成功,但是这种操作可能会污染其他分支。
8、 切换分支会改变的地方:
/**
* 查看哪些分支已经合并到当前分支
* 在这个列表中分支名字前没有 * 号的分支通常可以删除。
*/
git branch –merged
git branch -d 分支名
分支合并实战:
1、创建工作区,并初始化为git项目,在该工作区创建 A.txt 文件并提交。
2、 在master 分支修改 A.txt 文件。并提交。
3、 创建 p1 分支并切换到 p1 分支进行工作,创建 B.txt 文件,并提交。
分支指向如下图所示:
4、 主分支上创建并切换到新分支 hotbug,并修改 A.txt 文件,然后提交。
5、 在主分支上合并 hotbug 分支。
在合并的时候,有时候会出现"快进(fast-forward)"这个词。 这是由于当前 master 分支所指向的提交是你当前提交的直接上游,所以 Git 只是简单的将指针向前移动。
换句话说,当你试图合并两个分支时,如果顺着一个 分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的 将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)。
6、 切回 p1 分支继续工作。
这时 A.txt 是过时版本,没有包括 hotbug 分支修改的东西。又在该分支中修改 A.txt 文件,并提交。
我们在 hotbug 分支上所做的工作并没有包含到 p1 分支中。有两种解决方案:
7、 切回 master 主分支,准备合并 p1 分支。出现冲突,并准备解决冲突。
8、 解决冲突。(其实就是查看有冲突的文件,商议准备留哪一部分,进行修改即可)。修改完之后,提交。即可完成合并,并回到主分支。
9、 项目分支图如下:
冲突:
有时候合并操作不会如此顺利。 如果你在两个不同的分支中,对同一个
文件的同一个部分进行了不同的修改,Git 就没法干净的合并它们。 如上述操作:我们在hotbug分支和p1分支对同一行信息进行了修改。在合并它们的时候就会产生合并冲突,此时 Git 做了合并,但是没有自动地创建一个新的合并提交。Git 会暂停下来,等待你去解决合并产生的冲突。 你可以在合并冲突后的任意时刻使用 git status 命令来查看那些因包含合并冲突而处于未合并(unmerged) 状态的文件。在我们解决了所有文件里的冲突之后,对每个文件使用 git add 命令来将其
标记为冲突已解决。 一旦暂存这些原本有冲突的文件,Git 就会将它们标记 为冲突已解。
解决冲突:
- 第一步:编辑文件,删除特殊符号
- 第二步:把文件修改到满意的程度,保存退出
- 第三步:git add [文件名]
- 第四步:git commit -m “日志信息”
- 注意:此时 commit 一定不能带具体文件名
- 长期分支:稳定的代码版本。如下图中的 master、develop
- 特性分支:它是一种短期分支。用来开发新功能的版本。如下图中的:topic
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。
(1)分支原理
.git/refs
这个目录中保存了分支及其对应的提交对象。HEAD
这个文件中,保存当前指向的分支。有时,当我们在项目的一部分上已经工作一段时间后,所有东西都进入了混乱的状 态,而这时我们想要切换到另一个分支做一点别的事情。 问题是,我们不想仅仅因为过会儿回到这一点而为做了一半的工作创建一次提交。
# 存储
git stash
# 应用栈顶元素,但不会删除栈顶元素
git stash apply
git stash
实际上也会去提交,只是不会去出现在日志中,但是我们可以使用 查看完整日志的命令去发现它。
# 查看存储
git stash list
# 来应用储藏然后立即从栈上扔掉它
git stash pop
# 加上将要移除的储藏的名字来移除它
git stash drop 元素名
# 如果不指定一个储藏,Git 认为指定的是最近的储藏
git stash apply stash@{2}
1、主要命令总结:
# 1. 创建分支
git branch branchname
# 2. 切换分支
git checkout branchname
# 3. 创建&切换分支
git checkout -b branchname
# 4. 版本穿梭(时光机)
git branch branchname commitHash
# 5. 普通删除分支
git branch -d branchname
# 6. 强制删除分支
git branch -D branchname
# 7. 合并分支
# 7.1 快进合并 --> 不会产生冲突
# 7.2 典型合并 --> 有机会产生冲突
# 7.3 解决冲突 --> 打开冲突的文件 进行修改 add commit
git merge branchname
# 8. 查看分支列表
git branch
# 9. 查看合并到当前分支的分支列表,一旦出现在这个列表中 就应该删除
git branch --merged
# 10. 查看没有合并到当前分支的分支列表,一旦出现在这个列表中 就应该观察一下是否需要合并
git branch --no-merged
# 11. 可以查看每一个分支的最后一次提交
git branch -v
# 在分支上的工作做到一半时 如果有切换分支的需求, 我们应该将现有的工作存储起来
# 12. 将当前分支上的工作推到一个栈中
git stash
# 13. 分支切换,进行其他工作,完成其他工作后,切回原分支,将栈顶的工作内容还原,但不让任何内容出栈
git stash apply
# 14. 取出栈顶的工作内容后 就应该将其删除(出栈).
git stash drop
# 15. git stash apply + git stash drop
git stash pop
# 16. 查看存储
git stash list
2、分支的注意点:
【系列文章】
1. Git&GitHub(进阶)
2. java多线程
3. JavaScript 总结
4. SpringMVC(一)
5. SpringMVC(二)
6. 详解Linux(基础篇)
……
关注博主