12.3
基本概念
集中式(svn)
svn因为每次存的都是差异 需要的硬盘空间会相对的小一点 可是回滚的速度会很慢
优点:
代码存放在单一的服务器上 便于项目的管理
缺点:
服务器宕机: 员工写的代码得不到保障
服务器炸了: 整个项目的历史记录都会丢失
分布式(git)
git每次存的都是项目的完整快照 需要的硬盘空间会相对大一点
(Git团队对代码做了极致的压缩 最终需要的实际空间比svn多不了太多 可是Git的 回滚速度极快)
优点:
完全的分布式
缺点:
学习起来比SVN陡峭
git的配置
git --version 查看版本
git config --system/–global…
/etc/gitconfig 文件:操作系统中对所有用户都普遍适用的配置。若使用 git
config 时用 --system 选项,读写的就是这个文件。
~/.gitconfig 文件:用户目录下的配置文件只适用于该用户。若使用 git
config 时用 --global 选项,读写的就是这个文件。
.git/config 文件:当前项目的 Git 目录中的配置文件(也就是工作目录
中的 .git/config 文件)什么选项也不加,这里的配置仅仅针对当前项目有效。
每一个级别的配置都会覆盖上层的相同配置。
配置内容
第一个要配置的是你个人的用户名称和电子邮件地址。这两条配置很重要,每
次 Git 提交时都会引用这两条信息,说明是谁提交了更新,所以会随更新内容一
起被永久纳入历史记录。
$ git config --global user.name "damu"
$ git config --global user.email damu@example.com
要检查已有的配置信息,可以使用 git config --list 命令
删除配置信息 git config --global --unset user.email
底层命令(理解原理)
基础的 linux 命令
clear :清除屏幕
echo 'test content':往控制台输出信息
echo 'test content' > test.txt 在“Git Bash Here”的文件夹内创建一个.text文件,内容为引号内的部分。
ll :将当前目录下的 子文件&子目录平铺在控制台
find 目录名: 将对应目录下的子孙文件&子孙目录平铺在控制台
find 目录名 -type f :将对应目录下的文件平铺在控制台
rm 文件名 : 删除文件
mv 源文件 重命名文件: 重命名
cat 文件的 url : 查看对应文件的内容
vim 文件的 url(在英文模式下)
按 i 进插入模式 进行文件的编辑
按 esc 键&按:键 进行命令的执行
q! 强制退出(不保存)
wq 保存退出
set nu 设置行号
.git目录
hooks 目录包含客户端或服务端的钩子脚本;
info 包含一个全局性排除文件
logs 保存日志信息
objects 目录存储所有数据内容;
refs 目录存储指向数据的提交对象的指针(分支)
config 文件包含项目特有的配置选项
description 用来显示对仓库的描述信息
HEAD 文件指示目前被检出的分支
index 文件保存暂存区信息
区域
工作区(不会被保存)、暂存区(暂时保存几次修改)和版本库。
git对象(数据对象)
Git 的核心部分是一个简单的键值对数据库。你可以向该数据库插入任意类型
的内容,它会返回一个键值(hash),通过该键值可以在任意时刻再次检索该内容。
向数据库写入内容 并返回对应键值
存储为git对象
命令:
echo 'test content' | git hash-object -w --stdin
-w 选项指示 hash-object 命令存储数据对象;若不指定此选项,则该命令仅返回对应的键值(hash)
--stdin(standard input)选项则指示该命令从标准输入读取内容;若不指定此选项,则须在命令尾部给出待存储文件的路径
git hash-object -w 文件路径 存文件
git hash-object 文件路径 返回对应文件的键值 d670460b4b4aece5915caf5c68d12f560a9fe3e4
如果加-w,成功存储,那么.git/object 下的info和pack中会有内容。
一些解释:
- hash-object 是一个键值对。
- 加-w,既会存储,也会显示hash。
- | 表示两个指令一起执行。
- echo ‘test content’| git-hash-object -w --stdin (从控制台中拿内容)和 先输入echo ‘test content’ > test.txt ,再输入 git hash-object -w ./test.txt(从文件中拿内容) 的是一样的。 但是生成的hash不一样。
- 只有第一次创建文件时可以从控制台中拿内容,参考下方“对一个文件进行简单版本控制”,修改文件再次存储时,只能采用从文件中拿内容的方式。
- 关于warning:LF是windows中的换行符,CRLF是linux中的换行符,git认为空格不对,所以自动换成linux中的空格。
查询版本库内文件
命令:
find .git/objects -type f
返回:
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
这就是开始时 Git 存储内容的方式:一个文件对应一条内容。校验和的前两个字符用于命名子目录,余下的 38 个字符则用作文件名。
即:存储后的目录名=hash的前两位,文件名=hash后边的38位。
根据键值拉取数据
命令:
git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
-p 选项可指示该命令自动判断内容的类型,并为我们显示格式友好的内容
返回:
对应文件的内容
如果直接用cat,会显示乱码(因为被压缩过)
显示内部存储的对象类型
命令:
git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
cat-file -t 可以让 Git 告诉我们其内部存储的任何对象类型
返回:
blob
对两种存储方式(见上方粗体),都使用此命令,返回的都是blob,说明无论是存文件,还是存控制台中的内容,最终存到git仓库中都是一个git对象,这个git对象是blob类型的(是一个key-value键值对)。 所以git对象是存储内容的(但不能存储版本)。*
对一个文件进行简单的版本控制
### 创建一个新文件并将其内容存入数据库
命令:
echo 'version 1' > test.txt
git hash-object -w test.txt
返回:
83baae61804e65cc73a7201a7252750c76066a30
### 向文件里写入新内容,并再次将其存入数据库
命令:
echo 'version 2' > test.txt
git hash-object -w test.txt
返回:
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
注意两次的hash不一样
### 查看数据库内容
命令:
find .git/objects -type f
git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30
git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
利用 cat-file -t 命令,可以让 Git 告诉我们其内部存储的任何对象类型
备注:
- 称git对象不能存储版本的原因是:首先创建一个新文件,存储一次,更新一次,再存储一次…任何再创建一个新文件,存储一次,更新一次,再存储一次…此时项目的版本包含两个文件,但每个git对象都只能查看一个文件的内容。即git对象只能代表“文件的不同版本”,不能代表“项目的不同版本”。
- 在 Git 中,文件名并没有被保存——我们仅保存了文件的内容。
12.4
树对象
树对象(tree object),它能解决文件名保存的问题,也允许我们将多个文件
组织到一起。Git 以一种类似于 UNIX 文件系统的方式存储内容。所有内容均以
树对象和数据对象(git 对象)的形式存储,其中树对象对应了 UNIX 中的目录项,
数据对象(git 对象)则大致上对应文件内容。一个树对象包含了一条或多条记录(每条记录含有一个指向 git 对象或者子树对象的 SHA-1 指针,以及相应的模式、类型、文件名信息)。一个树对象也可以包含另一个树对象。
树对象相当于 “版本的快照”。
查看暂存区
git ls-files -s 可查看暂存区
构建
第一步:创建暂存区
利用 update-index 命令 为 test.txt 文件的首个版本——创建一个暂存区。并通过 write-tree 命令生成树对像。
命令:
① git update-index --add --cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt
文件模式为 100644,表明这是一个普通文件
100755,表示一个可执行文件;
120000,表示一个符号链接。
--add 选项:
因为此前该文件并不在暂存区中 首次需要—add
--cacheinfo 选项:
因为将要添加的文件位于 Git 数据库中,而不是位于当前目录下,所以需要—cacheinfo
② git write-tree
通过① ②,可构建一个树对象。
第二步:更新版本
新增 new.txt 将 new.txt 和 test.txt 文件的第二个个版本塞入暂存区。并通过 write-tree 命令生成树对象。
命令:
echo 'new file' > new.txt
用vim为test.txt 写入新的内容,更新到第二个版本:
git update-index --cacheinfo 100644 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
git update-index --add new.txt
git write-tree
第三步:更新树对象
将第一个树对象加入第二个树对象,使其成为新的树对象。
命令:
git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579 (树对象的hash)
git write-tree
read-tree 命令,可以把树对象读入暂存区
此处:
一般情况:
提交对象
相当于给树对象装了一个包裹,并提供了补充信息(作者、提交者、注释)
格式
提交对象的格式很简单:
- 它先指定一个顶层树对象,代表当前项目快照;
- 然后是作者/提交者信息(依据你的 user.name 和 user.email 配置来设定,外加一个时间戳);
- 留空一行,最后是提交注释。
创建提交对象
创建提交对象
echo 'first commit' | git commit-tree d8329f
(前面是树对象的hash)
(以后每一次都要求在后面加上 上一个提交对象的hash)
(hash不用输入完整)
返回:
fdf4fc3344e67ab068f836878b6c4951e3b15f3d
查看提交对象
git cat-file -p fdf4fc3
返回:
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579(-t 返回commit)
author Scott Chacon <schacon@gmail.com> 1243
committer Scott Chacon <schacon@gmail.com> 1243
first commit
接着,我们将创建另两个提交对象,它们分别引用各自的上一个提交(作为其
父提交对象):
echo 'second commit' | git commit-tree 0155eb -p fdf4fc3
cac0cab538b970a37ea1e769cbbde608743bc96d
echo 'third commit' | git commit-tree 3c4e9c -p cac0cab
1a410efbd13591db07496601ebc7a059dd55cfe9
注意:git commit-tree 不但生成提交对象 而且会将对应的快照(树对象)提交到本地库中
提交对象把右边的对象都封装了。
只需要明白:项目的版本是一个提交对象,但本质上版本的快照是树对象。
高层命令
新建目录(文件夹)——命名为workspace
git init 初始化仓库,生成.git目录
此时workspace目录,就是工作区
版本库在./.git/objects
创建暂存区后,在./.git/index
CURD
CURD指:增加create、检索retrieve、更新update、删除delete。
git add
git add ./
-
原理:将工作目录中修改的文件做成git对象放到版本库,再放到暂存区。
(工作目录→版本库→暂存区)
注意:此时版本库内是git对象! -
它会检查有几个文件发生了改变,工作目录中修改了几个文件,就生成几个git对象。并且生成git对象是增加,而不是覆盖(即它不会管你这个文件之前有没有git对象,它只管生对发生改变的文件生成新的git对象)。
git add 不仅可以把修改的文件放到暂存区,还可以把它们变成跟踪状态。
git commit
git commit -m “注释信息” 提交并加上注释
git commit -a 放到暂存区和提交到版本库一步完成(选项-a和-m可以一起用)
-a只能用于已跟踪过的文件
git commit 在vim中写完注释,再提交(用于注释很长的情况)
-
只有在提交时,会参照暂存区,做成一个树对象,放到版本库里面,然后拿出树对象,加上注释,成为一个提交对象。
-
一个完整的流程,至少包含git对象、树对象、提交对象各一个。一次提交只能有一个树对象和提交对象,git对象可以有很多。
以上总结:
git操作最基本的流程:
创建工作目录 对工作目录进行修改
git add ./(下面是此高级命令包含的底层命令)
git hash-object -w 文件名(修改了多少个工作目录中的文件 此命令就要被执行多少次)
git update-index ...
git commit -m "注释内容"
git write-tree
git commit-tree
跟踪文件
- 工作目录下面的所有文件都不外乎这两种状态:已跟踪 或 未跟踪
- 已跟踪的文件是指本来就被纳入版本控制管理的文件,在上次快照中有它们的记录,工作一段时间后,它们的状态可能是已提交,已修改或者已暂存所有其他文件都属于未跟踪文件。它们既没有上次更新时的快照,也不在当前的暂存区域。
- 使用 Git 时的文件状态变化周期如下图所示:
untracked 未跟踪 unmodified未修改(也可以是提交后的状态)
modified修改未暂存 staged 暂存未提交
使用git status查看文件的状态,红色代表修改未暂存,绿色代表暂存未提交,什么也没有,则是提交后的状态。
如果加到暂存区后还要修改,则修改后还要继续add放到暂存区。
git diff 查看哪些修改还没有暂存
git diff --staged 查看哪些修改以及被暂存了 还没提交
移除
git rm 文件名 删除工作目录中对应的文件 再将修改添加到暂存区(最后还需要commit提交)
运行 git rm 就相当于运行了下面两条命令:
$ rm README.txt README
$ git add ...
重命名
git mv 原文件名 新文件名 将工作目录中的文件进行重命名 再将修改添加到暂存区
运行 git mv 就相当于运行了下面三条命令:
$ mv README.txt README
$ git rm README.txt
$ git add README
查看历史记录
git log 显示全部信息
git log --pretty=oneline 放在一行内显示
git log --oneline 放在一行内显示,并且只显示hash的前几位
git log
git log --pretty=oneline
git log --oneline
12.5
分支
本质:分支是活动的指针,指向提交对象。提交对象一直往前更新时,指针也在一直往前更新。
为什么我们要分支:
分支可以新开辟一块空间做事情,不影响之前的代码。
这段代码写得好,就合并分支;写得烂,就删掉分支。
创建分支
命令:git branch
作用:
为你创建了一个可以移动的新的指针。 比如,创建一个 testing 分支:git branch testing。这会在当前所在的提交对象上创建一个指针
注意:
git branch 分支名 创建 一个新分支,并不会自动切换到新分支中去
git branch -b 分支名: 创建分支,并且切换过去
切换分支
git checkout 分支名
切换分支的坑:(第一次提交之前)在切换分支之前,要先确保状态“干净”(全部commit),否则会污染其它分支。
删除分支
命令: git branch -d 分支名(空分支可以用-d,有内容的分支应先合并变为空分支,再-d;未合并,不需要的分支的用-D强制删除)
(不能自己删自己,删除前要切换到别的分支)
查看项目分叉历史
如果直接查看日志,可能看不到某些分支内的历史,这个指令可以看到所有历史:
git log --oneline --decorate --graph --all
如果觉得太复杂,可以给指令“配别名”:
git config --global alias.别名 “原指令”
补充
git branch -v
可以查看每一个分支的最后一次提交(可以被上方指令取代)
git branch name commitHash(commit对象的hash)
新建一个分支并且使分支指向对应的提交对象(用于临时查看以前版本的代码,但是此时分支还没有切过去,得再用git checkout)
合并
git merge 分支名(会将这个分支合并到现在所处的分支内)
快进合并:由于当前 master 分支所指向的提交是你当前提交的直接上游,所以 Git 只是简单的将指针向前移动。 换句话说,当你试图合并两个分支时,如果顺着一个
分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的
将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分
歧——这就叫做 “快进(fast-forward)
以一个实例讲讲快进合并和典型合并:
①我现在正在写一个项目iss53,写到50%。
②现在紧急要求改一个bug。
③我先将50%的iss53提交,然后新建并切换到hotfix。
④现在把bug改好了,把hotfix合并到master,并删掉master(此时为快进合并)
起先:
移动指针:
合并完成:
⑤在 hotfix 分支上所做的工作并没有包含到 iss53 分支中。 如果需要拉取 hotfix 所做的修改,可以使用 git merge master 命令将 master 分支合并入 iss53 分支,或者也可以等到 iss53 分支完成其使命,再将其合并回 master 分支。
⑥此时的合并,是两个分支的合并,就可能会产生冲突:
修改成这样即可:
⑦再git add即可。
存储
有时,当你在项目的一部分上已经工作一段时间后,所有东西都进入了混乱的状
态,而这时你想要切换到另一个分支做一点别的事情。 问题是,你不想仅仅因为
过会儿回到这一点而为做了一半的工作创建一次提交。 针对这个问题的答案是
git stash 命令
git stash 命令会将未完成的修改保存到一个栈上,而你
可以在任何时候重新应用这些改动(git stash apply)
git stash list:查看存储
git stash apply stash@{2} :应用存储
(如果不指定一个储藏,Git 认为指定的是最近的储藏)
git stash drop 加上将要移除的储藏的名字来移除它
git stash pop 来应用储藏然后立即从栈上扔掉它(相当于apply和drop的结合,一般用这个)
后悔药
git checkout
命令:git checkout – 文件名
作用:将在工作目录中对文件的修改撤销。
注意:
git checkout – [file] 是一个危险的命令,这很重要。 你对那个文件做的任何修改都会消失 - 你只是拷贝了另一个文件来覆盖它。除非你确实清楚不想要那个文件了,否则不要使用这个命令。
git reset
命令:git reset HEAD 文件名
作用:将文件从暂存区中撤回到工作目录。
git commit –amend
命令: git commit --amend
作用:
这个命令会将暂存区中的文件提交。
如果自上次提交以来你还未做任何修改(例如,在上次提交后马上执行了此命令),那么快照会保持不变,而你所修改的只是提交信息。
如果你提交后发现忘记了暂存某些需要的修改,可以像下面这样操作:
git commit -m ‘initial commit’
git add forgotten_file
git commit –amend
最终你只会有一个提交 - 第二次提交将代替第一次提交的结果。
reset三部曲
git reset --soft HEAD~
~可以用^表示:
HEAD 表示当前版本
HEAD^ 上一个版本
HEAD^^ 上上一个版本
HEAD^^^ 上上上一个版本
可以使用 ~数字表示:
HEAD~0 表示当前版本
HEAD~1 上一个版本
HEAD^2 上上一个版本
HEAD^3 上上上一个版本
也可以回退至指定版本:把HEAD~换成commithash
还可以这样:
使用该命令之前的状态:
之后的状态:
该命令相当于git commit –amend ,版本库切回到上一个版本,此时用git log就会看不到v3版本,但是用reflog还是可以看到。
git reset [–mixed] HEAD~
–mixed为默认值,可以不写(HEAD~也可以不写)
该命令在切换版本库的同时,切回上一个暂存区。
可以把HEAD~换成treehash,回退至指定版本的暂存区。
三部曲中只有该命令后边可以加文件名,有几个文件名就将几个文件从暂存区回退至工作目录。
git reset --hard HEAD~
切换版本库的同时,切回上一个暂存区,还切回上一个工作区。
这意味着在工作区所做的一切工作会被覆盖,因此慎用。
git checkout commithash & git reset --hard commithash区别:
1. checkout只动HEAD --hard动HEAD而且带着分支一起走
2. checkout对工作目录是安全的 --hard是强制覆盖工作目录