1. Git 进阶使用
1.1. 版本历史更改
1.1.1. 最近一次 commit 的 message 修改
使用如下命令就可以对最近一次 commit 的 message 进行变更了
git commit --amend
1.1.2. 老旧 commit 的 message 修改 --- rebase + reword
输入如下命令
git rebase -i hash_value # hash_value,是需要的 commit 的父亲 commit 的 hash_value
之后会发生一系列交互
比如你想修改的是 9885fd5 这个 commit 对应的message,那么将 pick 改为 reword 或者 r(看下面的注释信息),然后保存退出。之后就跳到了修改 message 的文档了,在这里输入改变之后的 message 保存退出即可。
注意:这种方式修改之后会导致该 commit 及后面 commit 的 hash_value 都被改变掉,所以不适合团队集成开发中使用。
1.1.3. 把连续的多个 commit 合并成一个 --- rebase + squash
输入如下命令,
git rebase -i hash_value # hash_value 是要合并的 commit 的父亲 commit
输入之后会发生一系列的交互,如下所示,
假如我想要把上面两个 commit 合并成一个,需要使用 squash
,版本历史中较早的 commit 在上面,较晚的 commit 在下面,进行合并的话,是把较早的保留,较晚的合并到较早中去,所以要将上述两个 commit 进行合并的话,修改为如下所示,并保存退出
之后会进入另一个交互,在下面填写更改之后的 message 信息,保存退出之后即可。
1.1.4. 把间隔的几个 commit 合并成一个 --- rebase + squash
与上述类似,就是把间隔的 commit 移到一块即可。同样首先是输入如下命令,hash_value 是间隔多个 commit 中的最开始那个 commit 的父亲 commit 的 hash_value
git rebase -i hash_value
之后会进入交互界面,
比如想要合并 760df21 和 2234131 这两个 commit的话,那么修改为如下内容,保存退出即可
之后会进入修改 message 的页面,修改之后保存退出即可
1.1.5. rebase 其他操作
上述都使用了 git rebase
的命令,rebase 的意思是说改变基底,把版本历史中的某些 commit 给修改了。
git rebase origin/master # 把当前分支基于 origin/master 做 rebase 操作,也就相当于把当前分支的东西加到 origin/master 中
git rerere # 记录冲突解决的方式,然后可以在 rebase 的时候反复应用,可以和 rebase 结合用
1.1.6. 碎碎念
-
上述对 commit 的修改和合并,会导致该 commit 及后面 commit 的 hash_value 都被改变掉,所以不适合团队集成开发中使用。
-
假如 commit 修改和合并这一步操作完成之后不是直接进入修改 message 的页面,请根据提示进行进一步操作,比如
git rebase --continue
。 -
假如合并的包含了根 commit,那么 hash_value 则是根 commit 的 hash_value,之后在交互文档中把根 commit 填入即可,只需要填入要操作的名称和 hash_value 即可。
-
在合并 commit 中,假如一个 commit 没有被 pick 的话,比如注释了或者删除,那么在完成一系列操作中之后,这个 commit 将会被丢弃。如下图所示,275a765 这个 commit 被注释掉了的话,那么在完成之后,这个 commit 将会被丢弃。
1.2. 回滚操作 --- git reset
1.2.1. 暂存区恢复成和 HEAD 一样
git reset HEAD
git reset HEAD -- file_name1 file_name2 # 暂存区部分文件变得跟 HEAD 一样
比如一开始的话,HEAD 、暂存区和工作目录都是一样的,都是状态A,并且 readme*.md 文件都已经被跟踪起来了。下面我们修改 readme3.md 文件,之后把它 add 到暂存区。那么这样子工作目录和暂存区都是状态 B,而 HEAD 是状态 A。那么使用上述命令之后,会将暂存区恢复成状态 A。
1.2.2. 工作目录恢复为和暂存区一样
git checkout -- file_name # 工作目录中指定文件恢复为和暂存区一样
git checkout -- *|. ## 工作目录全部文件恢复为和暂存区一样
1.2.3. 回滚到某个 commit
git reset --hard hash_value # 把 HEAD、暂存区、工作目录都回滚到 hash_value 所代表的 commit 中。
git reset --hard # 把暂存区里面的修改去掉,也就是让暂存区、工作目录默认恢复到 HEAD 的位置
1.2.4. 碎碎念
-
工作目录内容的修改使用
git checkout
, -
暂存区内容的修改使用
git reset
-
版本历史的修改使用
git rabase
1.3. 工作目录、暂存区的更改状态保存下来
这个应用在开发中临时加塞了紧急任务的情况,可以把处理了一半,还在工作目录、暂存区的更改状态保存下来。
git stash # 把相应的修改内容给存下来,之后 git status 查看的话又变为什么都没改变的了
git stash list # 查看存下来的内容
git stash apply # 存下来的内容又恢复了,但是存下来的内容还在 stash 中
git stash pop # 存下来的内容恢复了,但是存下来的内容也没了
1.4. git merge
将两个分支或者两个 commit 进行 merge,merge 之后也会产生一个 commit
git merge branch_name1 branch_name2
git merge hash_value1 hash_value2
git merge --squash # 以 squash 方式进行 merge
在 merge 的过程中有时候会产生冲突,比如两个分支修改或者两个 commit 修改的是同文件的同一区域,那么就会发生冲突,那么会在相应文件冲突的地方有提示,大致如下所示。
<<<<<<< HEAD
windows
=======
root
>>>>>>> origin/master
通过协商和决定之后,确定文件最终内容,同时把上述内容删除。之后 git add
、git commit
即可。
- 不同人修改了不同文件不会产生冲突。比如说两个人维护一个仓库,一个人修改 A 文件,另一个修改 B 文件,那么 merge 的话不会产生冲突,直接将内容合并在一起。
- 不同人修改了同文件不同区域不会产生冲突。merge 的话直接将内容合并在一起。
- 同一文件改成不同的文件名会产生冲突。同上,一个人把文件名改成了 rename1,另一个人把文件名改成了 rename2,那么 merge 会发生冲突,需要进行协商确定最终的文件名。
- 不同人修改了同文件的同一区域会产生冲突。merge 的话因为不能确定保留谁的内容所以会产生冲突。
1.5. 分离头指针
分离头指针的例子如下所示,上面提到切换到某个分支的用法是
$ git checkout branch_name
那么假如把 branch_name 变成了 hash_value,那么这个就相当于“分离头指针”(PS:个人的理解是相当于创建了一个匿名 branch,这个匿名的 branch 是从 hash_value 的地方分出来的)
$ git checkout hash_value
之后的 commit 都是基于这个分离头指针的位置开始的,这些 commit 都没有基于某个 branch,相当于都是“游离”状态的。那么当切回到某个分支之后,这些 commit 都会被当成垃圾一样清理掉。如果这些 commit 很重要,那么请把这些 commit 跟某个 branch 绑在一起。
另外分类头指针也是可以用的,比如我们先用分离头指针进行一波修改和测试,如果测试不错,那么就把这些修改的 commit 添加成 branch。
1.6. .gitignore --- 指定不需要 git 管理的文件
git 更多是管理代码的版本控制,而代码构建出来的东西可以不用管理,因为这些是可以复现的。那么 .gitignore 这个文件就是告诉 git 哪些文件不需要被纳入版本控制系统的,也就是相当于会被忽视。
doc # doc 文件和 doc 目录都不会被管理
doc/ # 只指定 doc 目录不会被管理,但是 doc 文件没被说明,假如 doc 在的话,还是会被管理起来的
上面内容的有效范围是在整个项目中,即包括子目录等
.gitignore 可以借助 github 创建仓库时生成,常用的 .gitignore 如下所示,自己写的时候也值得参考一波:https://github.com/github/gitignore ;
另外想要 git 不管理某些文件,只能在 .gitignore 文件中指定;
1.7. 团队合作注意事项
如果你的 git 仓库是跟其他人一起维护的,那么请注意一下几点:
-
git push -f
不能使用-f 是 force 的意思,强制性把本地的 push 上去,就相当于本地的内容强制变成了远端的东西
-
公共的分支严禁拉倒本地做 rebase 操作的,因为一旦做了 rebase 操作之后,历史的 commit 就变了,但是其他人那边还是老旧的 commit,他们是基于老旧的 commit 做事情的,而你是几于新的 commit 做事情了,那么就相当于是两条分支了。所以记住了,不能向集成分支执行变更历史操作,建议的方式是在现在 commit 的基础之上再做调整。
比如 master 和 temp 分支刚开始指向的地方是一样的,如下所示,有两个用户都是基于 temp 分支做提交的
A 在 temp 分支上做 rabase 操作,那么 temp 分支就会和 master 分开。如下图所示,
然而在另一个人,还是基于如下情况做的 commit 等,那么这样,就出现问题了
2. git 中的对象及其操作
git 中的对象有三种:commit、tree、blob。下面对这三种对象进行阐述。
2.1. commit
每次执行git commit
都会创建一个commit对象,一个 commit 对象只包含一个 tree 对象,这个 tree 对象是 .git
所在父目录的对象, 那么这样子的话,一次 commit 就相当于把当前目录的情况给记录下来了。
2.2. tree
目录的对象,那么由于目录中可以包含目录,也可以包含文件,所以 tree 对象可以包含 tree 对象,也可以包含 blob 对象。
2.3. blob
blob 是一个文件的具体对象,比如png图像,css文件,这些文件都会对应一个blob对象,可以说是 git 对象中最基本的。另外,blob 跟文件名一点关系都么有,只要文件内容相同,不管文件名叫什么,blob 只有一份。
新建的Git 仓库中,有且仅有一个 commit,仅仅包含了 /doc/readme,请问内含多少个 tree,多个 blob?
含两个 tree,一个 blob。首先是 git 仓库所在目录是一个 tree (也就是 /doc/readme 的父目录),doc 是一个tree,而 readme 文件是唯一 blob。
2.4. 对象的相关操作
git cat-file -t|p|s hash_value # 显示版本库对象的内容,类型及大小信息
git cat-file -t hash_value # 查看版本库对象的类型
git cat-file -p hash_value # 查看版本库对象的内容
git cat-file -s hash_value # 查看版本库对象的大小
3. .git 目录探索
3.1. HEAD 文件
HEAD文件的内容显示了 HEAD 当前所指的分支信息,通过下面的内容可以佐证上面 HEAD 说到的一点:HEAD 指向的是某个分支,但通过查看分支文件内容可以发现里面其实是 commit 的 hash_value,也就是说 HEAD 实际指向的是某个commit。
使用 git checkout
命令之后,查看HEAD
文件的内容
3.2. config 文件
存放本地仓库(local)相关的配置信息,假如之前设置了 local 下的 user.name
,那么会在这个文件中存储相关的内容等信息。修改 config 文件中 user.name
配置项的内容,使用命令查看到的也是修改之后的。
3.3. refs 目录
3.3.1. heads 子目录
目录中包含的是各分支信息,每一个文件的内容都是 hash value,这个是值是该分支最后一次 commit 的 hash_value 值
.../.git/refs/heads# ls
master temp temp2
.../.git/refs/heads# cat master
9ef147d58eb7e09987cf5ce92254b1600ac92cd9
3.3.2. tags 子目录
里面显示的是标签的信息,Git 仓库可以有很多标签,项目开发到一定程度,是一个关键的成果了,比如开发到 v1.0,那可以打上一个标签了。
3.4. objects 目录
存放对象的目录。git 中的对象都是由 40 位字符组成,前两位字符用来当 object 目录中子目录名,后 38 位做文件名。