文章目录
- git 对象
- 通过git对象进行文件的保存
- git对象的缺点
- 树对象
- 构建树对象
- 提交对象
- 高层命令
- 工作区的文件状态
- git reset hard 咋用以及用错了怎么恢复
- git checkout vs git reset
- Git存储
- 后悔药
- 工作区
- 暂存区
- 版本库
- reset三部曲
- checkout深入理解
- tag
- 远程上的相关操作
- ssh登入
- 一些个人之前的理解(不建议看)
git config --global alias.lol “log --oneline --decorate --graph --all”
我这里下面所打的git lol这个命令,是重命名后的。
git 对象
Git 的核心部分是一个简单的键值对数据库。你可以向该数据库插入任意类型
的内容,它会返回一个键值,通过该键值可以在任意时刻再次检索该内容
git hash-object -w --stdin(这里可以写待存储的路径)
-w 选项指示 hash-object 命令存储数据对象;若不指定此选项,则该命令仅返回对应的键值
echo "test content" |git hash-object --stdin
通过echo将值由管道传递给后面,返回生成对象的hash值。内容相同,哈希相同。
执行上面的指令,并不会存储内容,.git/objects 里面若是存在。
echo "test content" |git hash-object -w --stdin
执行完上述命令,我们可以发现,哈希值的前两位作为文件夹的名称,文件夹底下的文件名就是后面的。
也可以就在黑窗口下面用find命令查找 -type f标识查找的是普通的文件。
哈希值:560a3d89bf36ea10794402f6664740c284d4ae3b
返回值:
输出一个长度为 40 个字符的校验和。 这是一个 SHA-1 哈希值
如何通过哈希值进行查看里面的内容:
- 通过cat命令查看,不行,因为文件的内容已经被压缩过了,会发现是一堆乱码。
- git cat-file -p + 哈希值。 通过这个命令就可以将文件的内容进行输出到终端。
-p 选项可指示该命令自动判断内容的类型,并为我们显示格式友好的内容
-t 就是展示哈希值的对应的数据类型。 下面展示的blog就是文本类型。
Git对象,就是由key:val 组成的键值对
通过git对象进行文件的保存
touch test.txt ,写入text.txt v1
git hash-object -w ./test.txt
会有一个警告,只是Linux和Windows下的换行不同,但是它会自动处理好,我们不关注。
返回的哈希值:
560a3d89bf36ea10794402f6664740c284d4ae3b
此时用find ./.git/objects/ -type-f 命令查看,可以发现已经有我们的git对象了,第一个是我们最新插入的,第二个是之前我们从标准输入得到的。
保存到git仓库的都是git对象,都是blob类型,也就是key:value的键值对。
如果此时我们向文件写入新的内容,会发生什么?
向文件里面写入test.txt v2 13:53
。
此时观察数据库有没有我们写入的新内容。
很显然,是没有的。
需要手动进行提交。
$ git hash-object -w test.txt
此时文件的哈希值:2a529bf61eee12dcacfb1a2d5ca0aef0eef93837为最新的文件哈希值。
此时再查看:
$ find ./.git/objects/ -type f
小结论:
git的提交不是增量,是压缩过后每一个文件。
git对象的缺点
上述操作的缺点: 每次都是我们自己生成git对象,纳入git管理,而我们的一次改动并不是一个版本,git对象不能代表我们项目的一次快照(即项目的整体的说明)。
- 记住文件的每一个版本对应的SHA -1 值并不现实,比如现在,你能记得test.txt对应的git对象是哪几个吗?
- Git中,我们仅保存了文件的内容,并没有文件名。我们的文件名存储的时候变成了SHA-1值,那么我们git cat-file -p xxx 的时候,若时间久了,看着文件的内容我们可能也回想不起来文件名是啥了。
上述操作全部是对本地的数据库进行操作,不涉及暂存区。并且我们的操作是本地数据库到版本库进行了操作!
上述的从./.git/objects看到的一个个SHA-1对应的就是一个文件对应的一个版本。
工作目录和版本库进行交互!
工作目录和版本库进行交互!
工作目录和版本库进行交互!
树对象
git对象后也称为数据对象。
树对象与暂存区有关系。
前面讲的是一个文件的版本,后面讲的是一个项目的版本。
构建树对象
前置工作,创建一个文件,写下若干内容,写入本地的数据库。
$ vim test.txt
$ git hash-object -w test.txt
获得的哈希值
560a3d89bf36ea10794402f6664740c284d4ae3b
查看数据库:
有一条数据
查看暂存区:
$ git ls-files -s
查出来啥都没有,因为确实啥都没有。
首次添加入暂存区的话 需要加入 --add --cacheinfo字段
$ git update-index --add --cacheinfo 100644 560a3d89bf36ea10794402f6664740c284d4ae3b test.txt
100644 指定的是文件的类型;
文件模式为 100644,表明这是一个普通 文件
100755,表示一个可执行文件;
120000,表示一个符号链接。
$ git ls-files -s
100644 560a3d89bf36ea10794402f6664740c284d4ae3b 0 test.txt
暂存区:
版本库:
说明上述的git update 命令只在版本库上添加了数据,而没有对本地数据库做操作。
而当我们执行:
$ git write-tree
返回一条哈希值:06e21bb0105e2de6c846725a9a7172f57dd1af96
查看哈希值对应的类型,是树对象。
此时.git当中也会多出一条记录。
执行了git write-tree后文件依旧在暂存区
版本库最终存在哪了?
存在了本地的.git/objects 里面!
他和我们之前的文件版本有什么区别?
我们先前执行的git update 命令不会对本地的数据库进行操作,而只是更改暂存区,当我们执行git write-tree的时候,才会将所有的操作的文件从暂存区记录到本地的数据库,此时的一条版本就是一个项目的版本!
git对象代表文件的一次次版本。树对象代表项目的一次次版本!
2.新增new.txt
$ echo "new file v1" > new.txt
生成树对象之前需要把文件存起来。也就是树对象之前需要的是git对象。
$ git hash-object -w new.txt
生成的哈希值:b2b44573b56f7ea44cbe6e34a69b4f1b19311c5c
查看版本库
**此时将text.txt 更改为v2版本 **
此时更改的文件需要重新生成一个git对象。
$ git hash-object -w test.txt
此时就可以查看到对应的git数据库已经有了这个git对象
上面的06:项目的第一个版本(树对象),56:test.txt文件的第一个版本(git对象),c3:test.txt的第二个版本(git对象),b2:new.txt文件是第一个版本(git对象)。
此时我认为我的更新已经足够一个项目了。
我们将进行如下操作:
$ git update-index --cacheinfo 10064 c31fb1e89d8b6b3ef34cdb5a2f999d6e29b822ba test.txt
注意:暂存区的内容会被覆盖!
如今暂存区还是一条记录!!
git update-index --add xxx文件 (即不需要–cacheinfo)
–add会做两件事情,将xxx文件生成一个git对象,放入版本库,并且放入暂存区。
由于我们上面已经手动将xxx文件生成了git对象放入了暂存区,所以我们现在也用不了。
$ git update-index --add --cacheinfo 100644 b2b44573b56f7ea44cbe6e34a69b4f1b19311c5c new.txt
$ git write-tree
返回的哈希值:1f71f517166b786be8b60549739af1d8ff23db8f
此时版本库当中就会有记录最新的项目快照。
覆盖的机制是由名称相同决定的!
树对象和git对象都是哈希,不好分辨。
树对象的缺陷: 需要记住树对象的hash值,并且需要记住他们的作用,这很难做到。
git cat-file -p 也可以查看树对象的内容。
提交对象
commit-tree 会生成一个提交对象。
$ echo "first commit" | git commit-tree 1f71f517166b786be8b60549739af1d8ff23db8f
返回的hash值:3828906e3173a6ad5321a5b6ee7548fee0ecef02
此时为commit对象。
此时有很多信息展示,并且包装了树对象。
第一次生成的时候不需要指定父的提交对象,后续需要,因为需要一条版本链,方便我们回溯。
即带上-p选项。如图类似~
相当于提交对象对树对象进行封装,并且是链式结构,并且拥有详细的注释信息。
总结:
git存的都是快照,而不是增量。
版本库在objects,暂存区是index文件,只要有东西就会生成index文件。
高层命令
首先我们创建文件,我们添加到暂存区的时候是通过git add命令,这个命令可是无人不知,无人不晓。
$ git add laofu.txt
但实际上他是将文件生成git对象放入版本库,然后从版本库从git对象放到暂存区当中。类似 git update-tree add 这个操作。
相当于git hash-object -w 文件名与git update-index --add --cacheinfo …
git add的时候树对象还没生成,此时只是加入了暂存区,只有真正提交的时候才会生成树对象。
git对象是每次都是一个快照。当加入暂存区之后都是安全的,此时git已经管理了,放到.git/objects当中。
此时已经有了暂存区。
此时就有三个文件了,一个git对象,一个树对象,一个commit对象。
此时缓冲区的文件也没有被删除。
git commit 相当于做了git write-tree 生成了树对象,然后git commit-tree 对树对象封装,添加了注释和版本链。
工作区的文件状态
两种:已跟踪,未跟踪。
没有git add就是未跟踪。
而已跟踪又分为三种状态:已提交,已修改,已暂存。
已暂存,已修改的状态是可以并存的,因为当前修改还未生成git对象。
这种情况我们首先要git add .,如果直接git commit就是把旧版本的进行提交,那可能就不是我们想要的结果。
git diff的使用:
由于git status 显示的太过简略,我们可以用git diff查看更多的信息。
- 我们可以使用git diff 不带参数显示哪些更新还没有暂存。
- git diff --cached 显示哪些更新已经暂存准备下次提交。
git commit -a
选项只要是之前加入过暂存区,后续更改的话,通过commit -a 就能够一起提交。
git rm 操作
实际上是新增了一个树对象,一个提交对象。
git log 命令查看到的hash值都是提交对象。
git log --oneline --graph --all
git reset --hard删除的文件,若是有git add,commit
是可以通过找到最近提交的那一次,然后git reset --hard 强制的恢复工作区的。
git reset hard 咋用以及用错了怎么恢复
个人的理解:
建议别用!
当从低版本想要恢复到高版本的时候,可以使用,但是也是说低版本的文件没有想要的,才更新上去。
但是若想从上往下,则确保当前工作的文件都是不需要,并且丢弃没有问题。
否则你会经历以下事情:
- 若你需要的文件是git add . git commit -m过的,那么还有的救,此时你git reflog,就可以看到commit的版本,此时往上进行git reset --hard 即可,这样工作区的文件就能够恢复了。
- 若你需要的文件只进行了git add,那么也是有救的,git fsck --lost-found 这个命令能够展示出一些没有在当前版本当中git对象,此时我们寻找Blob类型的对象,然后git cat-file -p + hash值查看里面的内容。但是实际上这样做也是很惨的,因为真正的项目当中的Blob对象可不止这么一点,一般来说这个时候真的是非常难受的!!
- 若你需要的文件没有经历git add,然后执行git reset --hard 发现不见了, 没救了,等死吧。
git fsck --lost-found 可以理解为git add 但是又没有出现在本次版本的文件吧~
总结:还是别用吧,虽然说有时候git reset --hard能够回到之前版本重新工作,保不齐返回了,这个时候有没有后悔药不一定了~
贴一个看到的博客~
https://www.cnblogs.com/hope-markup/p/6683522.html
查看缓冲区:
git ls-files -s 在项目变大得时候,这个文件里面得内容也会变得非常的多。
可以重定向到文件里面,然后进行查找,然后cat-file 查看里面得内容。
上述的做法也是不错的!
删除和重命名:
git mv 就可以实现重命名
git rm (–cached 或 -f)文件,区别就是对不对工作区的文件进行删除。
查询:
git diff :查看未暂存的修改
git diff --cache : 查看未提交的暂存
git status : 查看工作目录中文件的状态。
git log --oneline : 查看提交记录
git log --oneline --decorate --graph --all : 查看整个项目的分支图。
分支
分支的本质:HEAD指向的提交对象。
git branch name就是给提交对象取名称。
git branch 查看分支列表
git branch name:在当前提交对象上创建新的分支
git branch -v 查看分支最新的提交
git branch name commithash:在指定提交对象创建新的分支。
git checkout name: 切换分支
git branch -d name : 删除空分支
git branch -D name: 强制删除分支
git checkout vs git reset
版本库的东西只会变多,即.git/objects的东西只会变多。
切换分支动了三个地方,HEAD,暂存区,工作目录。
而版本库是不会动的。
当我所在master分支,创建分支ljhdev分支,在ljhdev底下创建文件,此时直接切换回master,会将文件带回给master里面。
- 此时版本库没有记录,只是单纯的将文件从ljhdev工作区拿到了master工作区。而如果master将该文件git add,git commit 过后,那么ljhdev将会失去这个文件。但是也可以去版本库里面找,这就比较蠢了。
- 并且若是不小心删除了ljhdev分支,那么文件就只能呆在master了,污染了master的工作目录。
而当ljhdev只是将文件进行了git add而没有commit的话,进行切换分支的时候,会发现我们master分支也会多一个文件。并且暂存区当中也会新增该文件。
并且切换分支的时候会影响master的暂存区,若matser此时进行git commit的时候,会导致ljhdev的暂存区对应的文件消失,以及工作区的文件消失。
总结:
在切换分支的时候,有未暂存的修改(第一次)或者有未提交的暂存(第一次)会把文件带过去,分支切换成功。
而若是已经提交过的文件,进行修改过后,此时编译器强制要求git add,git commit后才允许切换分支。
最佳实践:即每次git status 没有问题再选择切换分支当需要切换分支的时候,确保当前分支的git status是没有问题的再切换。
快进冲突: 不会产生冲突
典型合并:有机会产生冲突
解决冲突:
打开冲突文件,进行修改,git add + git commit即可。
版本穿梭: git branch name commitHash
当工作区的文件没有被git add,那么切换分支的时候会将该文件带走。 这样会污染其他的分支,所以切换分支前需要git status 查看工作区的状态。
git stash
典型合并:
像下面这种情况,由于ljhdev和master分支不在一个线上,所以当发生冲突的时候是会发生问题的。但不一定发生,只要没有修改到相同的文件。当分支图合并后的图形。
合并后想删除ljhdev分支就可以删除了。
git merge + 分支名称(ljhdev)
git branch -d 分支名称
一个分支指向的提交对象,所以也是哈希值。
heads 分支就是没有后缀名的文件,存着提交对象的哈希值。
HAED则是可变指针,指向目前的分支名称。
Git存储
若有任务已经做了一半,此时突然有一个紧急任务的情况。
git stash可以对文件进行一次栈上的保存。实际上是有做提交的。
git stash list 可以看到储存的内容。
查看完整的分支图即可查看到。
其他的分支也能查看到这条记录。
git stash apply只是运用栈顶元素,但是并不会删除。
git stash drop stash@{0} 即可删除栈顶元素。
git stash pop 可以运用并且删除,就能恢复之前的状态。
不同的分支都可以运用栈,并且可以删除别人的分支,不过都是本地操作,自己别删了就行~
git stash pop默认用栈顶,若是栈顶不是自己分支的,也可以使用,但是看你的需求了~
后悔药
工作区
git checkout -- 空格 <file>
可以撤回到该文件最近一次git add的版本。
暂存区
如何撤回自己的暂存
假如当前的文件内容如下:多写了两个@@
此时git reset HEAD a.txt 能够返回上一次提交对象里面的值。
此时暂存区的暂存恢复了上一个状态。
版本库
注释写错了:
git commit --amend ,然后就可以直接更改了。
内容写错了可以直接git add,暂存区自动会覆盖成最新文件。
内容忘记提交了git add 后,git commit --amend就可以啦~
reset三部曲
git reflog 只要动HEAD就会进行记录。
git reset soft HEAD~ 类似git commit --amend
1.重置HEAD
git reset [–mixed] HEAD filename ,这是唯一一个可以携带文件名的操作,如果不带,说明默认会将所有的文件从暂存区恢复到上一个版本。HEAD 换成别的实际上操作就很多了!
1.重置HEAD,2.重置暂存区。
git reset hard HEAD~ 跟checkout 比较相似,但是就是工作区没办法。因为master也跟着一起动了。
1.重置HEAD,2.重置暂存区,3.重置工作目录
特别说明:路径reset
git reset [–mixed] commithash filename
所有路径reset 不会动HEAD,HEAD本质是一个分支,分支是一个提交对象,提交对象指向一个树对象,树对象可能指向多个git对象,一个git对象对应一个文件。
所以HEAD可以代表一系列的文件状态。
所以HEAD是不会动的,只是用commithash的内容去更改其中的暂存区的内容。
checkout深入理解
git checkout branchname 和 git reset --hard commithash特别像,都需要重置HEAD 暂存区 工作目录。但是checkout对于工作区是安全的。
checkout + 路径
git checkout commithash filename 重置暂存区,重置工作目录。
git checkout --filename 重置工作目录。
tag
git tag 显示标签
轻量标签: 就是一个不会动的分支,特定分支的引用。
git tag + 名称,默认给当前分支打tag
git tag 标签名 hash值
git show 查看特定标签。
git tag -d 删除标签。
git checkout tagName 回到标签的版本。(会产生头部分离)
但是此时会头部分离,即需要创建分支。
git checkout -b 生成一个新的分支,就不会头部分离了,此时可以在v1.0进行更改。
git branch --merged 查看已经合并到当前分支的列表
git branch --no-merged查看没有合并到当前分支的列表。(观察是否需要合并)
远程上的相关操作
git remote add origin url 可以设置远程的分支,git remote -v可以进行查看,git push操作默认会生成远程跟踪分支,但如果没有数据想要push,并且需要远程跟踪分支,git fetch origin 可以将远程跟踪分支弄下来,此时git branch -u 就可以建立本地分支与远程跟踪分支产生同步关系,git push和git pull操作就很简单了。
本地没有分支的时候,也可以git checkout --track 远程跟踪分支(remote/分支名)
ssh登入
ssh-keygen –t rsa –C 你的邮箱
生成了公私钥,然后将公钥设置到gitee即可。
往后git clone 点击ssh的渠道,并且使用自己设置的密码即可。
一些个人之前的理解(不建议看)
这部分可以不看了,是之前自己遇到的一些东西,贴在这里给自己看~
-
git rm必须带参数。
-
什么情况使用git rm
git rm 的两种情况,一种–cached是删除暂存区的,但是不删除本地,而 -f 会将本地的也一同删除,并且即使回到之前的版本,该文件也没了。所以 -f操作比较危险。
已经加入了暂存区,并且想要删除后,本地想要删除,可以使用git rm
git restore --staged [file] : 表示从暂存区将文件的状态修改成 unstage 状态。当然,也可以不指定确切的文件
git restore [file] 只会修改工作区,不会修改暂存区。
可以看出,restore staged可以搭配restore 一同使用。
git checkout -b (git lol 先前的一次提交),然后返回master后,合并先前的分支,提示不能warning: refname ‘7654cca’ is ambiguous.
Already up to date.
回滚撤销 git reset 版本哈希值,之后用git restore 文件名就可以恢复git add前的并且没有更改资料的状态。
git restore:
若今天我修改文件的时候,修改错了,我添加了一句“我是傻逼”,一不小心git add . 放入暂存区了,此时暂存区的状态。
但是我肯定不是傻逼;
- git restore --staged test.cc 能够对暂存区进行回退;
- git restore test.cc 能够对工作区当中的test.cc进行回退,此时本地的和暂存区都不会出现“我是傻逼”这句话了。
这种情况是只进行了git add,没有进行git commit的。
那要是我们不小心git commit了该怎么办。
注意:git log --oneline 只有在git commit后才会生成一个版本。
此时进行一次git add .,重新提交后的页面如下。
此时有一个问题,就是上一次的提交已经是一个正确的提交了,那么我们要不要修改一下记录呢
git commit --amend; 就会自动打开一个文本,然后直接修改保存即可。
观察到 git commit 之后会改变版本的哈希值。
通过git reflog 可以看到这个记录实际上也并没有消失~
git tag 贴标签:
如果这次的提交是比较重要的,可以贴上标签,后续gitee上面也会有版本的显示。
git reset --soft HEAD~实际上只是进行了HEAD的更改,相当于是取消了上次commit,但是暂存区和工作目录的东西都还是上一次的。
单独执行该语句,此时相当于回到了git add后的状态。
git reset HEAD~ 等同于 git reset --mixed HEAD~
单独执行该语句,此时相当于回到了刚修改完工作区的状态。
git reset --hard HEAD~
单独执行该语句,比较危险,此时暂存区,HEAD,工作目录都会回到上一次,相当于啥都没了。并且他会强制的覆盖工作目录的文件。
git fetch 后只有一个分支,但是多了元素 origle/devFornws
git checkout devFornws 就是切换分支,此时进行git erge taobao/devFornws就可以将同事创建的分支拿到。
此时成员再做一次数据修改,并且git commit -a -m “” 把他上传。
那作为这个项目的主人,此时可以直接拿到数据。
origin 也可以理解为是一个服务器,就是我们常用的gitee,github上面创建仓库会有一个 xxx.git,实际上就是那个东西的一个引用。
origin 是远程分支的一个跟踪,这个分支是在我们本地的,远程分支实际上就是一个不可移动的本地分支,只有在进行网络通信操作的时候,远程分支才会产生移动。
也就是当你git fetch,在本地,你的版本链实际上是一前一后的,此时由于数据都在本地,你本地上面可以随意操作,当你觉得需要合并的时候,只需要进行git merge操作即可。
假设远程仓库在develop分支上面修改了一个文件,我们本地在master git clone 后,需要checkout develop 之后才能看到,无需带 -b 因为这个分支git clone 下来了。
更新远程仓库,就是拿我本地的内容去更新远程的仓库。 为啥不用git push呢?
git remove update origin
当我们处在自己的一个分支,比如我在devFornws 这个分支,master有一些内容需要同步过来,然后由我继续编写,此时我们使用git pull origin master 或者使用 git fetch origin master + git merge 最新的版本即可。下图为第二种情况,还没执行git merge 的情况。
生成远程跟踪分支的操作
git branch -a - v 可以查看有哪些是远程跟踪分支。
git push origin devforljh 能够生成远程跟踪分支
此时我们的git也会出现对应的分支。
重新理解git fetch 和 git pull
当我们使用git fetch
git branch -u origin/devforljh
这条指令可以让本地分支与远程跟踪分支同步。
同步了才可以直接使用git pull,否则需要git pull origin/devforljh
git branch -vv 可以查看是否有本地分支是否和远程跟踪分支产生了同步,蓝色字体就是有。
git pull 原理,拿远程最新的版本更新当前分支的版本以及远程跟踪分支的版本。
git fetch 则是将远程最新的版本更新远程跟踪分支版本。不会修改当前的版本。
这里的origin/devforljh 是指本地的远程跟踪分支。
origin devforljh 则是指远端的
将远程主机 origin 的 master 分支拉取过来,与本地的 brantest 分支合并。
git pull origin master:brantest
如果远程分支是与当前分支合并,则冒号后面的部分可以省略。
git pull origin master
小总结:
- 默认只有主分支上git clone 才会默认有远程跟踪分支。
- 若我在develop分支,若是git branch -u 指定远程跟踪分支,后续都可以使用git push ,而不需要指定是哪一个远程跟踪分支了。
- git pull 类似于 git fetch + git merge,git pull相当于将远程的内容弄到本地的远程跟踪分支和本地分支上,如果有冲突就会直接产生冲突,此时需要我们自己修改。
- 若是采用git fetch,则只有本地的远程跟踪分支的内容会和远程分支同步,而本地分支的指向不会改变。此时可以创建一个新的分支然后git merge,就可以不污染当前分支了。 推荐使用git fetch + git merge 的方式,他提供了更多的可能。
- git branch -u origin/devforljh 是表示将本地分支和本地的远程跟踪分支同步起来。
- git pull origin ,默认是将本地的远程跟踪分支和主分支更新为远程的主分支的内容。
- 当git pull origin master,表明将远端master分支 更新到当前分支。