Git内部状态管理和转换
本文主要来介绍一下 Git 的内部状态管理系统。它利用基于节点和指针的数据结构来跟踪及管理编辑操作的时间线。
一、本地仓库各种状态之间
对本地项目而言,任一时刻,Git 处于三种状态中的一种:工作区状态(workspace)、暂存区状态(index/stage)和提交区状态(local repo)。 下面利用新建项目来演示一下不同状态及其转换。
1. 新建并初始化项目Initialize the project
$ mkdir git_tree_test && cd git_tree_test
$ git init
提示:使用 'master' 作为初始分支的名称。这个默认分支名称可能会更改。要在新仓库中
提示:配置使用初始分支名,并消除这条警告,请执行:
提示:
提示: git config --global init.defaultBranch <名称>
提示:
提示:除了 'master' 之外,通常选定的名字有 'main'、'trunk' 和 'development'。
提示:可以通过以下命令重命名刚创建的分支:
提示:
提示: git branch -m <name>
已初始化空的 Git 仓库于 /Users/phillee/git_tree_test/.git/
$ git status
位于分支 master
尚无提交
无文件要提交(创建/拷贝文件并使用 "git add" 建立跟踪)
这时我们初始化了一个本地项目,默认处于 master 分支,尚无文件跟踪及提交。
2. 工作区The Working Directory
$ touch git_add_hello.txt
$ git status
位于分支 master
尚无提交
未跟踪的文件:
(使用 "git add <文件>..." 以包含要提交的内容)
git_add_hello.txt
提交为空,但是存在尚未跟踪的文件(使用 "git add" 建立跟踪)
现在我们为项目新增了文件 git_add_hello.txt ,尚未提交,当前位于工作区(Working directory)。这里颜色效果没出来,这时
3. 暂存区Staging Index
$ git add git_add_hello.txt
$ git status
位于分支 master
尚无提交
要提交的变更:
(使用 "git rm --cached <文件>..." 以取消暂存)
新文件: git_add_hello.txt
所有变动的文件,Git 都记录在一个区域,叫做”暂存区”(Staging index)。我们通过 git add 指令将工作区中的内容保存到暂存区,这时已经实现了对文件的跟踪,但还没有请求提交。这时候的文件已经被跟踪了,
4. 提交区Local Repo
$ git commit -m "init commit"
[master(根提交) 88b5382] init commit
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 git_add_hello.txt
$ git status
位于分支 master
无文件要提交,干净的工作区
暂存区保留变动的文件信息,等到修改结束添加到本地仓库”提交历史”(Commit history)中,这就相当于当前项目的一个快照(snapshot)。 项目提交历史就是由不同时间的快照构成。Git 可以根据此提交信息将项目恢复到任意一个快照状态。
5. 反向状态转换Reverse state switching
前面叙述并展示了三种状态之间的前向转换,现在我们反过来看一下,如何将当前状态转换成其父状态。
首先新建一个测试文件并利用该文件进行不同状态之间转换的实验,按步骤2到步骤4的方式将新创建的文件添加到提交区中。
$ touch gitadd_test_file
$ echo 'Hello world!' > gitadd_test_file
$ git status
位于分支 master
未跟踪的文件:
(使用 "git add <文件>..." 以包含要提交的内容)
gitadd_test_file
提交为空,但是存在尚未跟踪的文件(使用 "git add" 建立跟踪)
$ git add gitadd_test_file
$ git commit -m "add one file for test"
[master d97ee77] add one file for test
1 file changed, 1 insertion(+)
create mode 100644 gitadd_test_file
$ git log --oneline
d97ee77 (HEAD -> master) add one file for test
88b5382 init commit
- (1) 提交区状态转换成暂存区状态
现在我们尝试将已经提交 commit 但尚未 push 到远端仓库的状态返回到暂存区状态。此时的
$ git reset --soft HEAD^
$ git status
位于分支 master
要提交的变更:
(使用 "git reset HEAD <文件>..." 以取消暂存)
新文件: gitadd_test_file
如上结果所示,这时已经处于git commit命令之前的状态,达到此结果使用的是git reset --soft指令。
该操作会保留文件的改动及索引状态,撤销完成后将回到添加改动的状态。
此处由于提交历史比较简单,是单一分支一串到底,HEAD^和HEAD~都是指代上一次提交的提交区状态,也就是前一个代码快照。
注意git reset --soft与接下来要使用的git reset --hard之间的区别。此时的
- (2) 暂存区状态转换成工作区状态
$ git reset HEAD gitadd_test_file
$ git status
位于分支 master
未跟踪的文件:
(使用 "git add <文件>..." 以包含要提交的内容)
gitadd_test_file
提交为空,但是存在尚未跟踪的文件(使用 "git add" 建立跟踪)
通过git reset HEAD指令,我们得以将暂存区状态转换到工作区状态,也就是git add之前的状态。此时的
git reset其实默认是执行git reset --mixed指令的操作,没有指定soft/mixed/hard参数时即按照mixed参数执行。
- (3) 提交区状态转换成工作区状态
$ git add gitadd_test_file
$ git commit -m "add test file for git add test"
[master d535a57] add test file for git add test
1 file changed, 1 insertion(+)
create mode 100644 gitadd_test_file
$ git log --oneline
d535a57 (HEAD -> master) add test file for git add test
88b5382 init commit
$ git reset --hard HEAD^
HEAD 现在位于 88b5382 init commit
$ git status
位于分支 master
无文件要提交,干净的工作区
这里是将gitadd_test_file重新添加到暂存区,然后保存到提交历史。重新提交后SHA值会发生变化。然后从提交区状态直接返回到git add之前的工作区状态,使用的指令是git reset --hard,该指令强制将HEAD指针指向提交历史线中的前一个提交状态,会连同上次提交之后新建的文件一起全部撤销。如果没有新增文件只有改动,则改动会丢失,相对soft而言不够灵活该举动有一定风险,使用时要注意场合。当然即使这么操作了也并非就不能复原了,只是会多几步操作而已。
二、本地仓库与远程仓库各种状态之间
相比于第一部分的状态回滚,这部分内容属于广义上的状态切换了,相对比较容易理解掌握和应用。
假设本地仓库和远程仓库之间已经建立了连接(git remote add origin https://github.com/username/reponame.git),有以下三种状态转换。
- (1) 将远程仓库更新到与本地仓库状态一致
git push origin master
在本地仓库找到与 master 匹配的分支引用,用它更新远程仓库中的同名分支。 如果远程仓库不存在该分支,则会新创建分支并更新。master可以替换成其他分支名。还有一种更简便的方法,直接将当前所在分支的更新应用到远程仓库,使用HEAD指针:git push origin HEAD。
- (2) 拉取远程仓库更新到本地仓库
指令格式
git fetch <远程主机名> <分支名>
常见的使用场景如取回 origin 主机的 master 分支
git fetch origin master
该指令将远程仓库中与当前分支同名的关联分支内容更新到本地仓库。同时返回一个FETCH_HEAD指针,指向master在服务器上的最新状态,在本地可以通过它查看刚取回的更新信息。
注意与下一小节使用的命令git pull的区别,fetch指令只将远程仓库的内容拉到本地仓库,属于状态图中的Local repo阶段,并未应用到工作区workspace。
- (3) 拉取远程仓库更新到本地工作区
完整格式可表示如下,如果远程分支是与当前分支合并,则冒号后面的部分可以省略。
git pull <远程主机名> <远程分支名>:<本地分支名>
git pull origin master
将远程仓库中与当前分支同名的关联分支内容更新到本地并合并到当前工作区。其效果相当于git fetch origin master && git merge FETCH_HEAD,在git fetch从远程仓库的master分支拉取最新内容后将拉取下来的最新内容合并到当前分支的工作区中。
(全文完)
【原创】首发于博客园
本文作者 :phillee 发表日期 :2021年3月30日 版权声明 :自由转载-非商用-非衍生-保持署名(创意共享3.0许可协议/CC BY-NC-SA 3.0)。转载请注明出处! 限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。
感谢您的支持

微信支付