git -版本控制工具

Git 分布式版本控制系统

底层命令

git对象

  • git对象
  • 树对象
  • 提交对象
git对象
命令介绍
  • git init在工作区下,初始化仓库

  • echo “test” | git hash-object -w --stdin

  • -stdin (standard input)从标准输入流读取数据 ,也可以是文件路径(对文件进行版本控制)

    • -w 存入版本库(.git/object),若不加则不会存,仅输出哈希值
  • git cat-file -p 哈希值查看文件内容

测试
echo “test” | git hash-object -w --stdin  
bd721793c916757eb17ffc248799ad00f0b35735 (输出的哈希值就是"test"的唯一标示,只与内容有关,内容改变,哈希值也改变)
git对象.png

如图: 命名方式为 哈希值前位为文件夹,后面的为文件名

# 查看文件内容
git cat-file -p bd721793c916757eb17ffc248799ad00f0b35735
“test”

如果直接用cat 命令,则会乱码,证明内容被压缩过

cat .git//objects/bd/721793c916757eb17ffc248799ad00f0b35735
xK��OR04dx�0�$���Q�\.IR�% 

说明了一个问题,git将用户的文件或者内容,生成唯一hash值,并压缩数据

echo “test v1” > test.txt
git hash-object -w ./test.txt
89f9ceab790a0662e30ff1f2f3e8bac0bf7fd895 (输出,若文件内的内容仍然是test,则哈希值不变,证明,保存的仍然是文件内容)

若修改文件内容,git并不会自动记录修改内容,必须再显式提交一次

vim test.txt 
输入一些内容
git hash-object -w ./test.txt
会输出一个新的hash值

object目录下会多出来一个文件,也就是说git不是增量修改

git对象的问题:
  • 记住文件的每一个版本对应的hash值并不现实
  • 在git中,文件名没有被保存,仅保存了文件内容
  • 这些操作都是在本地数据库(git实质上就是一个简单数据库)操作,不涉及暂存区
  • git对象并不能表示项目的快照,仅代表每个文件内容的快照
  • 只能通过哈希值读取文件内容
树对象(tree object)

能解决文件名的保存,并且允许我们将多个文件组织在一起

命令介绍
  • git ls-files -s查看树对象的样子,(这个命令其实是查看暂存区的,树对象存在暂存区,git对象不涉及暂存区操作)
  • git update-index --add --cacheinfo 100644 bd721793c916 757eb17ffc248799ad00f0b35735 test.txt 创建树对象,不在版本库存储东西
    • –add:因为目前文件不在暂存区,首次创建树对象才需要

    • -cacheinfo:要把需要添加的文件存入数据库,而不是挡墙目录

    • 文件模式

    • 100644,普通文件

    • 100755,可执行文件

    • 120000,符号链接

  • git cat-file - p查看树对象内容
  • git read-tree -prefix=那么 第一棵树的哈希值 name表示新树的名字,将第二颗树加入第一棵树
测试如下
git update-index --add --cacheinfo 100644 bd721793c916757eb17ffc248799ad00f0b35735 test.txt
git ls-files -s 	#查看暂存区
100644 bd721793c916757eb17ffc248799ad00f0b35735 0       test.txt	#输出

find .git/objects -type f     #查看版本库        
.git/objects/bd/721793c916757eb17ffc248799ad00f0b35735	#输出
.git/objects/89/f9ceab790a0662e30ff1f2f3e8bac0bf7fd895	#输出
#版本库中并没有树对象
git write-tree       #将暂存区的内容写入版本库               
91db0f4cfb1a0eff06a0dd80db40069cebe97332 #树对象的哈希值
git cat-file -t 91db0f4cfb1a0eff06a0dd80db40069cebe97332
tree 	#文件类型,树对象

#此时版本库就有了树对象
find .git/objects -type f
.git/objects/bd/721793c916757eb17ffc248799ad00f0b35735 #"text"的哈希
.git/objects/89/f9ceab790a0662e30ff1f2f3e8bac0bf7fd895 #"test.txt内容的哈希"
.git/objects/91/db0f4cfb1a0eff06a0dd80db40069cebe97332 #树对象的哈希值

**说明:**git write-tree可以等树对象修改多次后,或者添加删除多次后在执行,这也就是说,树对象能代表项目的一个快照

**总结:**git对象代表每个文件的版本,树对象可以代表项目版本

树对象的构建

新建一个gyg.txt 修改 test.txt 将这两个文件塞入暂存区,并生成树对象

#新建gyg.txt
echo "new file v1" > gyg.txt
git hash-object -w gyg.txt  #添加新文件的git对象
b2b44573b56f7ea44cbe6e34a69b4f1b19311c5c #gyg.txt的哈希

#修改test.txt的内容
vim test.txt #加入test v2
git hash-object -w ./test.txt  #创建test.txt第二个版本的git对象
6623ae759d09947564e5b45ce93e2633f943cca2

#将test.txt第二个版本塞入暂存区
git update-index --add --cacheinfo 100644 6623ae759d09947564e5b45ce93e2633f943cca2 test.txt  

#查看暂存区
git ls-files -s                    
100644 6623ae759d09947564e5b45ce93e2633f943cca2 0       test.txt
#可以看出将test.txt的第一个版本的记录覆盖掉了(对比上面的记录)

#将gyg.txt塞入暂存区
git update-index --add --cacheinfo 100644 b2b44573b56f7ea44cbe6e34a69b4f1b19311c5c gyg.txt

#查看暂存区
git ls-files -s                
100644 b2b44573b56f7ea44cbe6e34a69b4f1b19311c5c 0       gyg.txt
100644 6623ae759d09947564e5b45ce93e2633f943cca2 0       test.txt
#证明,暂存区的覆盖是根据文件名覆盖的
#生成项目第二个版本的树对象
git write-tree                     
fe52097435752e39aded6f64dbb74746a96e5d4c

#查看版本库
find .git/objects -type f          
.git/objects/66/23ae759d09947564e5b45ce93e2633f943cca2
.git/objects/bd/721793c916757eb17ffc248799ad00f0b35735
.git/objects/89/f9ceab790a0662e30ff1f2f3e8bac0bf7fd895
.git/objects/fe/52097435752e39aded6f64dbb74746a96e5d4c  #workspace第二个树对象
.git/objects/91/db0f4cfb1a0eff06a0dd80db40069cebe97332 	#workspace第一个树对象
.git/objects/b2/b44573b56f7ea44cbe6e34a69b4f1b19311c5c

树对象目前的状态:

graph LR
	tree1(第一个树对象)-->git1[test.txt v1]
	tree2(第二个树对象)-->git2[test.txt v2]
	tree2(第二个树对象)-->git3[gyg.txt v1]
subgraph git
	git1[test.txt v1]
	git2[test.txt v2]
	git3[gyg.txt v1]
	end

将第一棵树加入第二颗树

#先查看暂存区
git ls-files -s                       
100644 b2b44573b56f7ea44cbe6e34a69b4f1b19311c5c 0       gyg.txt
100644 6623ae759d09947564e5b45ce93e2633f943cca2 0       test.txt
#执行加入操作
git read-tree --prefix=bak 91db0f4cfb1a0eff06a0dd80db40069cebe97332 
#查看暂存区变化,可以看出多出了一个新的记录
git ls-files -s                     
100644 bd721793c916757eb17ffc248799ad00f0b35735 0       bak/test.txt
100644 b2b44573b56f7ea44cbe6e34a69b4f1b19311c5c 0       gyg.txt
100644 6623ae759d09947564e5b45ce93e2633f943cca2 0       test.txt
#生成新的树对象
git write-tree                    
403127d92cb74fa3f505adc2d90e1c16932151ab
#查看版本库
find .git/objects -type f           
.git/objects/66/23ae759d09947564e5b45ce93e2633f943cca2
.git/objects/bd/721793c916757eb17ffc248799ad00f0b35735
.git/objects/89/f9ceab790a0662e30ff1f2f3e8bac0bf7fd895
.git/objects/40/3127d92cb74fa3f505adc2d90e1c16932151ab #第三棵树对象
.git/objects/fe/52097435752e39aded6f64dbb74746a96e5d4c #第二个
.git/objects/91/db0f4cfb1a0eff06a0dd80db40069cebe97332 #第一个
.git/objects/b2/b44573b56f7ea44cbe6e34a69b4f1b19311c5c
#查看第三课树的内容,如下:
git cat-file -p 403127d92cb74fa3f505adc2d90e1c16932151ab
040000 tree 91db0f4cfb1a0eff06a0dd80db40069cebe97332    bak
100644 blob b2b44573b56f7ea44cbe6e34a69b4f1b19311c5c    gyg.txt
100644 blob 6623ae759d09947564e5b45ce93e2633f943cca2    test.txt

此时树对象的状态:

​ tree3右边的其实就是第二棵树,本质上就是第二颗树通过bak指针,连接上了第一棵树

graph TB
	git4[第三颗树对象]--bak-->tree1
	git4[第三颗树对象]--gyg.txt-->git3
	git4[第三颗树对象]--test.txt v2-->git2
subgraph tree对象
	tree1(第一个树对象)-->git1[test.txt v1]
	tree2(第二个树对象)-->git2[test.txt v2]
	tree2(第二个树对象)-->git3[gyg.txt v1]
subgraph git对象
	git1[test.txt v1]
	git2[test.txt v2]
	git3[gyg.txt v1]
	end
	end
树对象的问题
  • 没有对每个版本所做的事,做出解释
  • 但是若要重用,必须记住哈希值
  • 解决方式:提交对象
提交对象
代码说明
  • commit-tree 树对象哈希值 创建提交对象
测试
#前面的表示对这次提交的注释
echo "first commit" | git commit-tree 91db0f4cfb1a0eff06a0dd80db40069cebe97332
7a2b96724dc580540d292862a013a72376a43624
#查看提交对象
git cat-file -p 7a2b96724dc580540d292862a013a72376a43624
tree 91db0f4cfb1a0eff06a0dd80db40069cebe97332	#树对象
author seulG <gengyagexstar1@gmail.com> 1574947028 +0800	#配置好的用户名和邮箱
committer seulG <gengyagexstar1@gmail.com> 1574947028 +0800
first commit	# 注释

也就是提交对象对树对象做了一次包裹,对树对象作出解释,每个提交对象都需要一个父对象(第一次除外)

# 生成第二个提交对象,指定父提交
 echo "second commit" | git commit-tree fe52097435752e39aded6f64dbb74746a96e5d4c -p 7a2b96724dc580540d292862a013a72376a43624
 #这是输出
c9712137be81be7d4cd61640911a5b00ef2f91e3
# 生成第三个提交对象
 echo "third commit" | git commit-tree 403127d92cb74fa3f505adc2d90e1c16932151ab -p c9712137be81be7d4cd61640911a5b00ef2f91e3
 #输出
b91c0ac905ff7fee128de2cf33c3b7923343959e
graph LR

subgraph 提交对象
commit1[first commit] --> tree1
commit2[second commit] --> tree2
commit3[third commit] --> git4
commit3[third commit] --父提交--> commit2
commit2[second commit] --父提交--> commit1
subgraph tree对象
	git4[第三颗树对象]--bak-->tree1
	git4[第三颗树对象]--gyg.txt-->git3
	git4[第三颗树对象]--test.txt v2-->git2
	tree1(第一个树对象)-->git1[test.txt v1]
	tree2(第二个树对象)-->git2[test.txt v2]
	tree2(第二个树对象)-->git3[gyg.txt v1]
subgraph git对象
	git1[test.txt v1]
	git2[test.txt v2]
	git3[gyg.txt v1]
	end
	end
	end
说明

真正代表一次项目版本的是提交对象,而代表一个项目快照的是树对象,提交对象只是对树对象的封装,但是提交对象的是链式

至此所有的底层命令已经结束,git数据库的存储不是增量的,需要回退版本,只需要直接找到所需要的版本的提交对象的哈希值,直接跳过去就行

目录说明

.git目录

在文件夹下,执行git init 就能初始化一个git仓库,生成一个.git隐藏目录

dir.png

hooks ——> 客户端或者服务端的钩子脚本

info ——> 包含全局排除文件

logs ——> 保存日志信息

objects ——> 存储所有数据内容(版本库)

refs ——> 存储指向数据(分支)的提交对象的指针

config ——> git配置选项

description ——> 对仓库的描述

HEAD ——> 指示目前被检出的分支

index ——> 暂存区保存暂存数据(暂存区)

git

工作目录

建议配合下面的高层命令看这一部分

  • 相当于沙箱环境,只要没有git add 或者git hash-object,可以随便修改
  • 工作目录下的文件只有两种状态 已跟踪 未跟踪
  • 已跟踪的文件有三个状态,已提交,已修改,已暂存
    • git add 使文件成为已暂存
    • 暂存完,又修改变,该文件,此文件会出现两个状态
      • 一个为已暂存
      • 一个为已修改
    • git commit 使文件变为已提交
    • git diff查看当前哪些更新未暂存
    • git diff –staged 或者 git diff –cached 查看哪些已暂存,等待提交
  • 可以用git status 查看当前文件状态

高层命令

git init 初始化仓库

git add

测试
git init
echo "hello world" > gyg.txt
# 查看暂存区
git ls-files -s                     
100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0       gyg.txt
# 查看该git对象内容
git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad
hello world

git add 将工作目录的修改做成git对象放进版本库,再放入暂存区

说明
  • 对工作区修改几个文件就会生成几个git对象
  • 一个对象,修改n次,就出生成n次对象,git不是增量的,而是直接生成新的git对象,旧的git对象也不会删除,会保留在版本库(.git/objects)
  • git是绝对安全的,尽管没有提交,加入到了暂存区,使文件变为已暂存状态,git也能管理,不会丢失数据(找到对应的哈希值就能找回数据)
  • 只有在提交的时候,才会去参照暂存区,做成一个树对象,放入版本库
  • 在你想要添加注释信息的时候,才会把树对象拿出来,添加注释,将其包装成提交对象
  • git add 实则上执行了 git hash-object -w,和git update-index --add --cacheinfo

git commit -m “注释”

测试

git commit -m "注释"
[master(根提交) 3b52ce7] “注释”
 1 file changed, 1 insertion(+)
 create mode 100644 gyg.txt
#查看暂存区,可以看到没有变化
git ls-files -s                        
100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0       gyg.txt
#查看对象数,可以发现有三个对象,一个git对象,一个树对象,一个提交对象
find .git/objects -type f          
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/3b/52ce7e550af40b74c470afdd1675cf492a3cbe
.git/objects/d0/0870593909003f4669d40cd6d65d0bcd081efe

说明

  • 可以不加-m参数 , 在打开的vim编辑器里面编辑比较长的注释

  • 可以跳过使用暂存区,加上-a选项,git会将所有已跟踪的文件,暂存起来一并提交

  • 提交正如上述,版本库的树对象拿出来,然后,添加注释,包装成提交对象

  • git commit 实则上做了git write-tree将暂存区的写入版本库,git commit-tree生成提交对象

  • 使文件变为已提交状态,若所有文件都是已提交状态,则 git status不会显示任何内容

git status

查看当前文件状态

  • git commit 使文件变为已提交
  • git diff查看当前哪些更新未暂存
  • git diff –staged 或者 git diff –cached 查看哪些已暂存,等待提交

测试

echo "file1" > file1.txt
git add file1.txt
git status
#输出
1 file changed, 1 insertion(+)
 create mode 100644 file1.txt
#修改文件
vim file1.txt 加入file1 v2
git status
#输出
要提交的变更:
  (使用 "git rm --cached <文件>..." 以取消暂存)
        新文件:   file1.txt
尚未暂存以备提交的变更:
  (使用 "git add <文件>..." 更新要提交的内容)
  (使用 "git restore <文件>..." 丢弃工作区的改动)
        修改:     file1.txt
#出现了两个该文件的状态

git diff
#输出
diff --git a/file1.txt b/file1.txt
index e212970..98e6ed6 100644
--- a/file1.txt
+++ b/file1.txt
@@ -1 +1,2 @@ 
 file1
+file1 v2 
#file1.txt已修改,未暂存
git add file1.txt

个人理解总结

git对象

git对象(blob类型)

也可以成为数据对象,这个对象只组织数据,不会记录文件。也就是说这个对象只会记录每个文件的内容,并且根据内容生成hash,存入git的数据库(键值对数据库),这就有一个问题,git对象只能代表每个数据的一个版本,而无法代表一个文件的版本。

树对象(tree类型)

树对象,能将git对象组织起来,并将文件组织起来,从而能够代表一个项目的版本。对于树对象的操作,仍然只能通过hash操作,并且无法显式知道每次对文件作出了什么修改。

提交对象(commit类型)

提交对象,将树对象封装上自定义的说明,以及git的一些配置信息,如提交用户,提交用户的邮箱。并且会带有其父提交对象的hash(第一个提交对象除外),可以通过git commit-tree hash -p hash指定父提交对象。能够真正代表一个项目的版本,我们只需要访问提交对象就可以访问到这个版本的所有文件信息以及注释。注意,提交对象是链式的,这就很容易进行回滚版本,直接指向对应的提交对象就相当于回退版本

git三大区域

暂存区

暂存区存放的是待提交为数对象的git对象,或者是树对象。可以用git ls-files -s查看暂存区的内容。使用git update-index --add --cacheinfo 文件类型[100644普通文件,100755可执行文件,120000符号链接] git对象hash [name]将一个git对象提交到暂存区,如果对一个git对象进行了修改,并且生成了新的git对象,希望将修改后的git都想提交到暂存区,则可以去掉--add参数,并且用同样地名字,进行覆盖。同样可以使用git read-tree prefix=[name] 树对象hash将一个输对象以name的名字提交到暂存区。注意,每次执行write-tree都会将暂存区的所有内容生成一个新的树对象。

注意暂存区的名字默认与文件名字相同,也就是说暂存区的内容是与文件相对应的

版本库

版本库存储了每个提交的三大对象,git对象 、树对象、提交对象这些东西都存储在.git\object文件夹下,且命名方式为,每个对象的hash的前两位作为文件夹,后几位为文件的名字。

工作区

工作区就是项目的目录,项目目录的文件除了.git文件夹的内容,只存在两种状态,未跟踪,和未跟踪。工作区是一个沙箱环境,只要是没有 将文件提交给git管理,文件可以做任何修改,这就是未跟踪状态(就不能进行版本控制)。通过git add或者git hash-object将文件提交给git管理,该文件就是已跟踪,此后文件所做的修改都要存入版本库(git对象不是增量的好处),就可以进行数据的回滚。

已跟踪的文件存在三种状态,已提交,已修改,已暂存。

git高层命令

git add

git add做了以下几件事

  1. 先将修改的数据生成git对象,并存入版本库
    • git hash-object
  2. 后将该git对象从版本库存入暂存区
    • git update-index --add --cacheinfo

git commit

git commit做了以下几件事

  1. 将暂存区的内容生成树对象
    • git write-tree
  2. 将树对象和注释 包装为提交对象
    • git commit-tree
  3. 提交并不会清空暂存区
  4. 若一次要写的提交注释太长,可以用git commit会打开设定的默认编辑器

git status

会给出工作区的所有文件所处的状态

  • 注意每次修改文件,git并不会自动提交修改的文件,需要手动git add,也就是说,修改已追踪的文件,必须要手动git add 该文件,否则直接提交并不会提交该修改。

  • 已跟踪的文件应该执行提交。

  • 已修改的文件应该执行git add,或者进行暂存,或者执行git restore进行回退。

  • 一个文件可以存在两种状态,在git add后,该文件被标记为已暂存,然后修改该文件,该文件会被标记为已修改,同时存在两种状态。此时提交,只会提交修改的。

  • 因为git status显示的信息过于简单,可以通过git diff --staged查看已经暂存待提交的文件

  • 跳过暂存区,我们可以使用git commit -m -a跳过使用暂存区,直接把所有已跟踪的文件加入暂存区,并提交,节省了git add的步骤

git 删除操作

当删除了工作区的某个文件,该文件就会被标记为已删除,可以通过git add ./将修改存入暂存区,如果此时提交,用户执行的是删除操作,但是git在版本库种执行的仍然是新增操作,新增了一个不包含删除文件的树对象,和该树对象的提交对象

当然上述操作,可以用git rm实现,git rm 会删除指定文件,并将修改存入暂存区,只需要提交修改即可

git 修改文件名字

直接将文件修改名字,git会将该文件标记为已删除和未跟踪,但是当我们执行git add ./时,git会自动比较两个文件的内容,内容一直就会将该文件标记为renamed

使用git mv快速实现上述操作,能重命名文件,并将修改存入暂存区

git 查看历史记录

git log会显示每个提交对象的hash,注释,以及做了什么操作,作者信息和时间

git log --pretty=oneline或者git log --oneline,将每次提交放在一行显示,会隐藏作者信息,并且会显示提交对象hash的前几位

git分支

简介

git的分支就是一个指针(HEAD)。HEAD是一只指向提交对象,每次进行提交操作,HEAD都会进行更新,上面也说了,提交操作是链式的(通过其父提交对象组织),因此HEAD指针可以很方便的进行版本的回退和切换分支,只要让HEAD指向不同分支的提交对象即可。

每个分支的最新提交对象的hash只存储在ref目录

模型

git的分支极其高效,我们可以把该模型理解为:每次对项目文件的修改都存在一个链表中,默认一个master分支每次指向最近加入链表的提交对象,HEAD指向master,只有被HEAD指向的指针才能移动,因此创建分支就相当于新加入了一个指针(锚点),如果新增代码,该分支(锚点)就往前移动,而其他分支由于失去了HEAD的指向,而不能移动。当新的分支做的不行的话,就可以让HEAD指针直接指向master,从而实现代码回退,做的可以就可以直接让master指向新的分支,这就是代码的合并。而这一切建立的基础就是,git的版本库是增量的,每次的修改都是一次新的提交,不会去覆盖之前的提交。

命令

  • 显示分支列表git branch

  • 创建分支git branch,会在当前所在的提交对象上创建一个指针,但是此时HEAD并没有指向新的指针

  • 切换分支git checkout,会让HEAD指向指定的分支

    • 加上-g参数表示新建分支并且换
    • 分支切换会改变HEAD,工作目录的文件,和暂存区的内容
    • 如果git不能干净利落的切换分支,git会禁止切换分支
      • 在新分支修改了项目文件,没有执行git add就会禁止切换分支
    • 如果git在新分支上创建了新的未跟踪文件,git允许切换分支,但是该文件也会随着进入新的工作区
    • 最好的方法,每次切换分支查看状态,无状态才切换分支
  • 删除分支git branch -d不能删除当前HEAD所在的分支

    • 若该分支确定不用合并则用-D参数
  • 查看完整分支历史git log --oneline --decorate --graph --all

    • 注意老分支看不到完整地log日志,上述命令才可以
  • 查看每个分支最后一个提交git branch -v

  • 新建一个分支指向指定的提交对象git branch commitHash

    • 可以变相的用于回退版本
    • 时光机,版本穿梭

配置别名

git config --global alias.lol “log --oneline --decorate --graph --all”

将上述命令直接用别名 lol替换,不能添加git

合并分支

在分支测试通过之后,就可以将分支合并到主分支上。只能是其他分支,合并到主分支。

快进合并

切换分支步骤:

  1. 先切换到主分支
  2. 执行git merge [branch]将[branch]合并到主分支
  3. 合并完的分支可以删除,留着无意义
  • 直接将master指针指向要合并的分支
  • 会造成 合并前目标分支与master分支之间所产生的分支过期

典型合并

  1. 回到主分支
  2. 合并分支
  3. 会自动合并代码的修改
    • 遇到冲突会报错,只能手动解决
    • 手动解决的方式,就是手动打开冲突文件,进行修改,然后git add ./就代表解决了冲突
    • 然后git commit就行了

遇到合并冲突的情况,两个平行分支同时修改了同一个文件的同一行代码,注意若是修改同一文件,不一定会造成冲突。

git存储

当你在一个分支做了一些工作后,突然想切到其他分支工作,而又不想为一次没有完成的工作做一次提交,这时就可以用到git存储。git存储会把未完成的修改存到一个栈上,我们可以在任何时候重新应用这些改动。

  • git stash 存储
  • git stash list 查看git存储

git存储,本质上是git帮我们做了一次提交,但是该提交没有被记录在日志,而是被存贮在一个栈中。

  • git stash apply stash@{2} //可以从git stash list查看存储,若不指定,git默认应用最近的一次存储
  • git stash drop stash@{2} //应用完存储就可以删除该存储

以上操作我们通常使用git stash pop应用并立即删除该存储

git后悔药

撤回工作区的修改

当已被跟踪的文件被修改,想要撤回修改,可以使用git checkout --file,就可以撤回对该文件的修改。

撤回暂存

当文件已经被暂存git add,这是文件就被存入了暂存区,此时如果想要撤回该暂存就可以使用git reset HEAD file,可以取消暂存。将该文件从已暂存的状态切换为已修改,此时如果想要撤销文件修改,可以参照 撤回工作区修改

撤回提交

  1. 注释写错:当注释写错,就不适用于上面的重新提交。此时可以使用git commit --amend就可以重新修改日志,并自动提交
  2. 提交内容错误:如果提交了错误的内容,要先修改错误内容,然后将其加入暂存区,之后再执行git commit --amend

没有真正撤回提交的方式,只是提供给了我们一次重新写注释的情况

reset三部曲(后悔药的实现机制)

  1. 移动HEAD(包括分支),checkout是只移动HEAD,而分支指针不会移动。
    1. git reset --soft HEAD~HEAD~是当前分支上一次提交的别名,页可以直接用提交对象的hash
    2. 这种操作,就相当于撤销了一次提交,因为他是连带分支指针一起前移
    3. git commit --amend所做的操作的实现原理,就是--amend做了一次git reset --soft HEAD~然后在重新提交(会出现分叉)
    4. 可以通过git reflog查看HEAD的变化
    5. git reset --soft只动了HEAD和分支指针,暂存区和工作目录不受影响
  2. 既修改HEAD(包括分支)也修改暂存区,不修改工作区
    1. git reset [--mixed] HEAD~注:mixed是默认选项,可以不填
    2. 此操作不会改变工作区,能撤销暂存区操作
  3. 同时修改暂存区、HEAD、工作区
    1. git reset --hard HEAD~,既修改HEAD(包括分支),也重置暂存区,也修改工作区
    2. 也是reset里面唯一的危险用法,是git能真正销毁数据的几个操作之一,如果工作区存在未跟踪的文件,或者未提交,已修改,但是没有被commit的文件,这个命令会强制覆盖工作区,会销毁上述所说的数据。
    3. 在所有数据都commit之后,运行此命令是安全的,因为commit对象会保存工作区的内容。
    4. git checkout branch的实现原理,只不过checkout不会改变分支,checkout命令给出了相应的保护机制,他不会将未被提交的文件删除,而是带入目标分支。并且,不会移动分支指针
  4. 路径reset(只有 --mixed可以跟文件名)
    1. 如果给reset提供了一个文件路径,他就会跳过移动HEAD,只修改暂存区
    2. 其实就是用HEAD里面的内容覆盖掉暂存区的内容(此时HEAD,指向的上次提交,也就是说,HEAD保存了上次提交的暂存区信息)
    3. 路径reset的作用范围是仅有该文件的暂存区,不会影响其他文件
    4. 这就是回退暂存区的实现原理
  5. git checkout file
    1. 相比于git reset --hard commithash --filename跳过了修改HEAD,暂存区,只修改了工作目录。类比于reset路径。只不过这个命令无法通过语法检查。
    2. 本质上是用HEAD里面的工作区信息区强制覆盖工作目录
    3. git checkout commitHash file只会跳过移动HEAD,修改暂存区和工作目录
    4. git checkoutgit reset --hard的变种

数据恢复

当出现了某种状况,我们重置了某个分支,或者删除了他,但是事后又要恢复他。

  1. 如果是硬重置,直接在reflog里面找到对象的提交对象,再硬重置回去就行
  2. 如果是要恢复已删除的分支,也是只需要在reflog找到对应的提交对象,用“版本时光机”,直接用该提交对象的hash创建一个分支就行(更常用)

tag

列出标签

git tag {-l tagname*}列出tag或者指定的一个或多标签

创建tag

git tag tagname commitHash,给指定的提交对象打上tag

查看特定标签

git show tagname,列出该标签所在的提交对象的信息

删除tag

git tag -d tagname删除指定标签

检出标签

git checkout -b tagname此时,HEAD会指向该标签所在提交对象,但是没有创建新分支,处于“分离头指针”的状态,如果此时作出了某些修改,并提交,,tag不会变,但是新的提交不属于任何分支,并且无法访问,除非用hash访问。如果是要修复某个tag版本的错误,则要创建新的分支(指向该标签后,加上**-b**参数),去修复错误,并将修复后的分支合并到主分支

钩子函数(Husky)

必须先初始化git仓库,然后安装Husky

Husky

安装npm install husky --save-dev,该模块使用来给git创建钩子的,会默认注册很多钩子

// package.json
{
  "husky": {
    "hooks": {
      "pre-commit": "npm test",
      "pre-push": "npm test",
      "...": "..."
    }
  }
}

需要在package.json添加上面内容

pre-commit提交前执行脚本

pre-pushpush之前执行的脚本

更多钩子查看[githooks]:https://git-scm.com/docs/githooks,但是不支持服务端钩子和pre-receive update post-receive

配合Eslint进行代码检查

.gitignore

配置git忽略接管的目录

团队协作

邀请对方协作

  • 配置远程仓库别名
    • git remote add [别名] 仓库地址
    • 这就将远程仓库用别名代替
    • git push 上传数据
    • git pull 拉取数据
  • 远程跟踪分支
    • 当我们clone一个git仓库,会默认创建一个origin/master远程跟踪分支
    • 可以使用git branch -vv查看远程跟踪分支
    • 当我们用git push 远程库别名 本地分支将本地分支推到远程仓库时,会创建一个远程跟踪分支,但是本地分支没有和该远程跟踪分支绑定
    • 如果此时我们想要把本地修改提交到远程仓库,可以直接git push,前提(已经和远程跟踪分支绑定)
    • 由上述可以看出,如果想要直接git push就必须让本地的分支关联远程跟踪分支。使用git branch -u [仓库别名]/远程仓库分支名将本地已存在的分支绑定远程跟踪分支
      • git checkout -b 本地分支名 远程跟踪分支名新建分支并且绑定远程跟踪分支
      • git checkout --track 远程跟踪分支会创建一个鱼远程跟踪分支名字一样的本地分支,并且绑定该分支,前提是先把远程库fetch下来
    • 删除远程分支git origin --delete [分支名]
    • 列出仍在远程跟踪,但远程分总已被删除的无用分支git remote origin --dry-run
  • 冲突
    • 当同时修改同一个文件同一行的时候,后pull的会产生冲突,这样要先将前者的修改pull下载,然后询问对方是否可以修改,自己在本地进行修改后,在提交,然后push上去。这就会被标记为已解决冲突
    • 当远程存在文件,我们又在本地创建了相同文件,需要拉取数据,我们可以先提交,push上去,然后按照上述操作,解决冲突

pull request (fork流程)

想要参与某个项目但是没有权限

  • 参与者先fork 该仓库生成自己的远程仓库,然后去clone自己fork的仓库
  • 然后作出修改,提交到自己的远程仓库
  • 此时可以请求合并自己作出的修改
  • 我们要同步本地和原项目的代码,需要配置别名git remote add [别名] 原项目的地址
    • 此时可以重新设置master的远程跟踪分支,先拿到该仓库的更新git fetch 远程仓库别名,需要那个就fetch哪个,然后切换当前分支的远程跟踪分支git branch -u 远程仓库别名/分支名,然后git pull拉去数据