1 Git相关概念
1.1
以下所谈三个区,文件并不只是简单地在三个区转移,而是以复制副本的方式转移

使用 Git 管理的项目,拥有三个区域,分别是
- Working area工作区(亦称为 工作树Working Tree)、
- stage area 暂存区(亦称为 index 索引)、
- Git 仓库
对应地,Git 中的文件有三种状态
- modified 已修改 :若工作区的文件被修改了,但还没有放到暂存区,就是 modified 状态。
- staged 已暂存 :若被修改的文件已经从工作区到了暂存区,就是 staged 状态,因此我们也可以说,文件处于暂存区 = 文件是 staged 状态。此外,Git 会为 staged 状态的文件打上标记,以使其包含在下次 commit 的列表中
- committed 已提交 :表示文件已经 "复制一份" 到了本地 Git 仓库中
1.2 HEAD、工作树Working Tree、分支Branch、索引
Working Tree :实际就是 working area
Branch:branch可以有多个,其本质上是一个指向 commit 对象的可变指针
HEAD:HEAD只能有一个,其本质上是一个指向 正在工作中的本地 branch 的可变指针
简单来讲,就是你现在在哪儿,HEAD 就指向哪儿
更具体来说:HEAD指针指向我们所在的 branch,当我们在某个 branch 上创建新的 commit 时,branch 指针总是会指向当前 branch 的最新 commit
所以,HEAD指针 --------> branch 指针 --------> 最新 commit
例如当前我们处于 master 分支,所以HEAD这个指针指向了master分支指针

2. 命令
2.1 add 命令
add是个多功能的命令,主要有如下 3 个功效:
① 可以用它开始跟踪新文件,并放到暂存区
② 把已跟踪的、且已修改的文件放到暂存区
③ 把有冲突的文件标记为已解决状态
此外,当存在多个文件需要添加到暂存区时是,采用
shell
git add .
git commit 默认会将暂存区所有文件一并 commit
2.2 commit
Git 标准的工作流程是工作区 → 暂存区 → Git 仓库,但有时候这么做略显繁琐,此时可以跳过暂存区,直接将工作区中的修改提交到 Git 仓库,这时候 Git 工作的流程简化为了工作区 → Git 仓库
Git 提供了一个跳过使用暂存区域的方式, 只要在提交的时候,给 git commit 加上 -a 选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add 步骤:
shell
git commit -a -m "日志信息"
2.3 status
git status 命令无法直接显示 committed 文件的状态,它主要关注的是当前工作目录和staging area 中文件的状态。
当执行 git status 时,会显示以下信息:
- 已修改但未 add 到 staging zone 的文件
- 已 add 到staged zone 但未 commit 到 Git 仓库的文件
- untracked 文件(即不在版本控制下的新文件)
对于已经 commit 的文件,如果它们没有新的未 commit 的改动,git status 将不会报告它们的存在,因为它默认那些文件干净的。
2.4 log和reflog
shell
#log 命令仅能查看现存分支
#查看现存版本记录
#显示内容过多会自动进入多屏显示控制方式:空格向下翻页 、b 向上翻页 、q 退出
git log
#每个历史压缩到一行内显示,此时只显示 hash 值和备注
git log --pretty=oneline
#与上面的区别是 hash 值只显示一部分
git log --oneline

shell
#reflog 命令能查询到所有分支(包括无法被恢复的历史分支)
git reflog

3. 删除操作
删除也分几种情况。
3.1 手动删除本地文件
首先,我么应把删除也视为一种【修改】。
因此,如果文件已经纳入 Git 管理,我们右键删除了文件后,能在 Git中查询到记录,并且为了完成删除,我们需要 add 和 commit这个删除操作
比如,我们直接右键删除文件,此时用status命令应查到:
add commit 完成删除操作,并且能在日志查询到该记录
3.2 用命令删除
shell
#从 Git 仓库、暂存区、工作区中同时移除对应的文件,即 index.js
git rm -f index.js
#只从 Git 仓库和暂存区中移除指定的文件,但保留工作区中的文件,即 index.css 文件
git rm --cached index.css

D 代表已删除,??代表 untracked,即只存在于工作区,等待 add
4. 版本控制
版本控制其实无非就是对commit、修改、删除的一种回退,并且,回退这三者的操作是一致的
4.1 reset 命令

- reset 命令是无法撤销的 ,即无法用 reset 命令 reset 上一次 reset。
这句话意思是,当我们使用 reset 命令回退版本后,既无法通过 log 命令查询这次回退的本身的记录,也无法查询回退跳过的记录,更无法通过 reset 命令,撤销上一次 reset。
不过,我们仍然可以使用 reflog 查到 reset 记录,但这些记录无法用于还原
例如:
在下面的例子中,Git 仓库中已经对 html 和 txt 文件都完成了同步。
此时,我们使用 --hard 进行全局回退,当回退完成后,以外部视角来看,既看不出使用 reset 命令进行过回退,也不知道曾经存在过 有 txt 文件的一个分支

reflog 查询到所有分支(包括历史分支) ,因此能查询到 reset 记录。但如果此时输入 git reset --hard head~22 该代码无法执行,根本原因是现在已经不存在这些分支了。
--soft 实际应用场景:
修改 commit 信息:commit的代码是正确的,但是想修改 commit 附带的信息,可以使用 git reset --soft HEAD^ 命令来重置分支指针,并修改提交信息,然后重新提交,因为工作区和暂存区还是最新的代码。
合并 commit :之前的 commit 都是正确的,每次 commit 一句诗,依次 commit 了四次,这种情况下,工作区、暂存区、Git 仓库都是4句诗。但是,我们想合并四次 commit 为一次。由于工作区和暂存区都保存了正确的四句诗,我们用 --soft 将 Git仓库回退到0句诗,再 commit 一次暂存区,这样,就将四句诗合并到一次 commit 了。
--mixed 实际应用场景:
**取消暂存区的修改:**如果你不小心将修改添加到了暂存区,可以使用 git reset HEAD 命令将指定文件的修改从暂存区移除,然后重新add + commit 。此时,该命令与git restore --staged 作用类似
举例来说,Git 仓库中有1,2 号诗,工作区错误写成了 1,2,4号诗,并且还 add 到了暂存区。此时,mixed 模式将Git 仓库和工作区回退到2号阶段(Git本来就是 2 号,因此不发生改变),然后再将工作区改为1,2,3号并 add commit,这样,最终工作区、暂存区、Git 仓库都同步为了正确的1,2,3
撤销部分 commit:如果你只想撤销部分 commit ,可以使用 git reset --mixed HEAD~n 命令将最近的 n 次 commit reset 为指定commit,然后手动 add 需要保留的修改到暂存区,最后 commit。
--hard 实际应用场景:
撤销错误的提交 :如果你提交了错误的代码,可以使用 git reset --hard HEAD^ 命令来撤销提交并删除所有的修改,然后重新提交正确的代码。
回退到历史版本:如果你想回退到某个历史版本,并且不需要保留任何修改,可以使用 git reset --hard 命令来重置当前分支到指定的提交。
4.2 restore

对于第4/5条,简单演示如下:
创建了一个 txt 文件,并且已经同步到 Git 仓库成为第二个版本
此时目标是 仅在工作区中 删除 txt 文件
使用restore -s HEAD~1,指定回复上一个版本的 txt 文件覆盖到工作区,而显然上个版本并不存在 txt 文本,因此自然就删除了该版本工作区中的 txt 文本,且不回退 Git 仓库。
4.3 应用
4.3.1 工作区、暂存区、Git 仓库已经同步的情况下,误删除了本地文件(即工作区文件)
目的是恢复工作区文件,显然,用 restore 命令的1、2、4、5都可以
例如,。先将 txt文件 add 并 commit,然后右键删除本地文件,此时
shell
#该命令仅撤销工作区的修改,由于三库已经同步,又仅仅误删了工作区的文件,因此用不带参数的restore
git restore a.txt

4.3.2 修改了工作区中的文件,且 add 到了暂存区,但是想要撤销这个add
目的是撤销暂存区的修改,但Git仓库和工作区保持不变,此时用3,还可以用以前的reset
例如:不小心在文件中写了 "老板是个大煞笔"!并且已经 add 到暂存区了!如果再继续commit 的话,第二天就面临失业的风险!
Changes to be committed 代表该文件在暂存区中等待 commit
(1)git restore --staged
首先人家已经给了提示:use "git restore --staged ..." to unstage ,意思就是这句命令可以撤销 add 这个操作

执行带有 --staged 参数的命令后,再查看状态,Changes not staged for commit 代表该文件在工作区中等待 add
已经成功撤销暂存区中的内容。
(2)git reset HEAD 【file name】
前述已经提到,这种方法只能撤销暂存区的修改,与我们的需求完全一致
shell
git reset HEAD index.txt
4.3.3 删除了工作区中的文件,且最终 commit 到了 Git 仓库,但是想要撤销这个 commit
其实就是全局回退,restore 命令已经无法实现了
利用上述提到的 reset --hard 实现
5. 忽略文件、版本号、标签
5.1 忽略文件
一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。 在这种情况下,我们可以创建一个名为 .gitignore 的配置文件,列出要忽略的文件的匹配模式。
文件 .gitignore 的格式规范如下:
① 以 # 开头的是注释
② 以 / 结尾的是目录
③ 以 / 开头防止递归
④ 以 ! 开头表示取反
⑤ 可以使用 glob 模式进行文件和文件夹的匹配(glob 指简化了的正则表达式)
星号 * 匹配零个或多个任意字符
abc\] 匹配任何一个列在方括号中的字符 (此案例匹配一个 a 或匹配一个 b 或匹配一个 c) 问号 ? 只匹配一个任意字符 两个星号 \*\* 表示匹配任意中间目录(比如 a/\*\*/z 可以匹配 a/z 、 a/b/z 或 a/b/c/z 等) 在方括号中使用短划线分隔两个字符, 表示所有在这两个字符范围内的都可以匹配(比如 \[0-9\] 表示匹配所有 0 到 9 的数字) ### 5.2 版本号 每一次Commit对应一个 40 位长的版本号,在对更改内容使用 SHA -1 加密算法后得到的。 这样,根据版本号,可以避免内容被篡改。 其次, 根据版本号的前两位,在.git/objects 文件夹中,可以找到本次 Commit 的记录。 ### 5.3 tag 标签 简单的理解,tag 就是 对某次 commit 的一个标识,相当于起了一个别名。 例如,在项目发布某个版本的时候,针对最后一次commit 起一个 v1.0.100 这样的标签来标识里程碑的意义。 #### 5.3.1 tag的类型 有两种类型的标签 : 轻量标签(lightweight)、附注标签(annotated) 【轻量标签 】: 只是某个commit 的引用,可以理解为是一个commit的别名; 【附注标签】 :是存储在git仓库中的一个完整对象,包含打标签者的名字、电子邮件地址、日期时间 以及其他的标签信息。 它是可以被校验的,可以使用 GNU Privacy Guard (GPG) 签名并验证。 #### 5.3.2 ```shell git tag : 直接列出所有的标签 git tag -l xxxx : 根据 xxxx 进行标签的筛选 git show 标签名 : 查看标签的信息,(轻量标签 和 附注标签 的信息是不一样的) git tag 标签名 : 直接给当前的提交版本创建一个【轻量标签】 git tag 标签名 提交版本号 :给指定的提交版本创建一个 【轻量标签】 -a : 理解为 annotated 的首字符,表示 附注标签 -m : 指定附注信息 git tag -a 标签名称 -m 附注信息 :直接给当前的提交版本创建一个 【附注标签】 git tag -a 标签名称 提交版本号 -m 附注信息 :给指定的提交版本创建一个【附注标签】 git tag -d 标签名称 : 删除指定名称的标签 ```  ## 6.分支 **分支在实际开发中有什么用呢?** 假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。 现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样既安全,又不影响别人工作。 ```shell git branch 列出本地已经存在的分支,并且当前分支会用*标记 git branch -r 查看远程版本库的分支列表 git branch -a 查看所有分支列表(包括本地和远程,remotes/开头的表示远程分支) git branch -v 查看一个分支的最后一次提交 git branch --merged 查看哪些分支已经合并到当前分支 git branch --no-merged 查看所有未合并工作的分支 git branch 【新分支名称】 创建新分支 git checkout 【分支名称】 切换分支 git checkout -b 【新分支名称】 创建分支的同时,切换到该分支上 git merge 【想要合并进当前分支的分支名】,将指定的分支合并到当前分支 ``` ### 6.1 创建分支 #### 6.1.1分支的进一步认识 首先回顾概念 Branch:branch可以有多个,其本质上是一个指向 commit 对象的可变指针 HEAD:HEAD只能有一个,其本质上是一个指向 正在工作中的本地 branch 的可变指针 既然 branch 指向 commit 对象,而 HEAD 又指向 branch,那么此时 branch 和 HEAD 在第一次 commit 前都是空指针。 在 Git 中,当git init 后,是不存在任何分支的。 第一次 commit 之后,系统会自动创建一个 名为 master 的分支,然后 HEAD 指向 master,master指向最新的commit。  分析: log打印现存的所有分支,但由于没有任何 commit,因此自然没有分支,因此提示为空 同理,因为不存在任何 commit,自然无法创建新分支指针,因为不知道指向哪里。 #### 6.1.2 创建分支示例 创建新分支之前至少要存在一个 commit,并且创建的新分支自动指向最新的 commit。 init 之后新建了一个 a, 将 a commit 后自动创建了一个 master 分支指针指向这个 commit 然后手动创建一个 new 分支,该分支会指向最新的 commit,即现在唯一的一次。  在 new 这个分支下,新建一个 b,并最终 commit   此时,再切换回 master 分支,会发现 b 消失了   ### 6.2 合并分支 按照开头所述,其实合并分支往往分为两步。 前提:我们已经从远程仓库中拉取了完整的代码 1. 比如,我在本地分支dev开发完一个功能后,先要把dev合并到本地的master分支, 2. 然后将dev push 到远程仓库 #### 6.2.1 合并本地分支 上述,master 分支中有 a,new 分支中有a,b。 其实就是模拟工作中,从仓库拉取了完整代码,并且自己新建了一个分支,完成了自己的工作(b),现在要将这个成果合并到 master 分支中。 ```shell #首先确认是在master分支中 git checkout master git merge new ``` 分支冲突的情况: 例如另一个人比我们先一步,将同名的 b 合并到了 master。(两个 b 中的内容并不一致)  打开 master 中的 b,已经自动生成了对比。 确定想要的内容后,在 master 分支中最终 commit 即可。  ### 6.3