HEAD分支本质上是个活动的指针,固定指向你当前的最新提交对象,HEAD默认是master,可切换,非HEAD分支会停留在创建该分支是的提交版本里面,这样HEAD每次都是指向最新的提交对象,其它分支停留在了其它时间点的提交对象中,当你这个HEAD分支代码写烂掉了可以直接切换到原来的版本中去
HEAD文件:记录当前所在的分支,比如master refs:存储当前git的指针
git branch test #没有指明提交对象则是在当前分支上
git branch test xxxxx(commit对象的前6位hash值),含义是在指定的commit对象上创建分支
但是我们通过git lol命令查看时发现HEAD依然是指向HEAD,下次提交指向最新ccommit对象的HEAD依然是master 切换分支到test
git checkout test
创建分支并且立即切换过去
git checkout -b test
然后我们做第四次提交,HEAD指向test,带着test分支移动到9018bbb这个版本,master则是停在了ed8803b这个版本
插曲: 如果上次提交后未对工作区作修改的话再次提交那么这次提交不会被记录数,工作区,暂存区,版本库都不会有新变化 伴随着提示
On branch test
nothing to commit, working tree clean
#分支test工作区目录是干净的(潜台词是自上次提交到现在连工作区都没有动过,更不用所暂存区,所以没有什么可以修改的)
git add两个左右,①会把该目录所有未跟踪的文件标记为已跟踪②把当前目录下所有文件即项目的一次性快照放入到暂存区,工作区没有变化 => 暂存区的快照和上次一样 => 生成的tree也一样 => 生成的commit对象也一样 => 操作日志也不再记录了
分支删除
git branch -d test #删除test分支
注意,如果当前HEAD指向了test分支,那就无法删除,报错信息
error: Cannot delete branch 'test' checked out at 'D:/devlp/idea2020.2/demo/test'
我们把分支切回master继续删除test如下
git checkout master
git branch -d test
给了以下警告,也就是说分支还没被合并,如果此时删除那你之前的工作
error: The branch 'test' is not fully merged.
If you are sure you want to delete it, run 'git branch -D test'.
需要我们使用强制删除命令才能成功删除
git branch -D test
我们强制删除一个分支,不代表会丢失修改,因为版本库已经有相关commit对象了,我们所丢失的是这个分支的相关记录,比如提交和回退等记录
git branch -v #查看各个分支的最新提交版本
其实通过git lol也能看到
分支切换注意点 前言:如果当前文件中有未跟踪时我们是无法提交成功的,等价于我们上面讲的,第一次提交后没有对工作区做任何修改再次提交也不会展示提交记录;如果是有处于已修改状态的文件可以通过git commit -am命令来提交; 切换分支不仅是版本回退到你指定的commit对象,工作目录也会恢复成你对应版本的工作目录,这个原理是依据暂存区的内容来恢复你的工作目录,因为暂存区代表你当前提交版本的项目快照,反之我们可知,在我们切换分支时,但如果是存在未跟踪或已修改状态的文件,这种情况对应以下几种问题
切换分支意味着会改动三个地方
HEAD指向commit对象、暂存区、工作目录
①存在未跟踪的文件时,直接切到master上,可以切换成功也不会报错,但我们查看当前工作区目录发现new.txt出现在了master分支的工作目录上,实际上此时master分支已经是被污染了,这是git的安全机制,即git宁愿让你切换的分支收到污染也不会说丢失这个文件,因为即便是在test分支上,因为new.txt是未跟踪文件,也没有被纳入git管理,如果为了防止污染分支不保留这个文件到切换目标分支的工作目录下,这个文件就会大概率丢失掉,比较丢失文件的后果往往大于分支被污染的后果
②存在已暂存但未提交状态的文件时 首先master分支的暂存区里只要一个test.txt文件 切回test分支把未跟踪文件存到暂存区,不提交然后直接切换分支到master,此时test分支暂存区是有两个文件的 切换分支后,我们发现master文件的暂存区 那这样master分支就被彻底污染了,而且伴随有提示
Switched to branch 'master'
A new.txt
如果是真实的开发环境,那就会导致master分支存在大量无关文件这种问题,也就是说你未提交暂存区那么当前暂存区的版本快照还不属于任何的正式版本(commit)对象,此时切换分支导致的问题是切到别人的分支,所以引出情况三 ③已提交的文件(clean working tree)的工作状态下修改,
总结:切换分支前一定要保证当前分支是最干净的,即全部文件处于已提交状态
git实操
工作流:
1.开发某个网站。
2.为实现某个新的需求,创建一个分支。
3.在这个分支上开展工作。
正在此时,你突然接到一个电话说有个很严重的问题需要紧急修补。 你
将按照如下方式来处理:
1.切换到你的线上分支(production branch)。
2.为这个紧急任务新建一个分支,并在其中修复它。
3.在测试通过之后,切换回线上分支,然后合并这个修补分支,最后将改
动推送到线上分支。
4.切换回你最初工作的分支上,继续工作。
首先,我们假设你正在你的项目上工作,并且已经有一些提交
现在,你已经决定要解决你的公司使用的问题追踪系统中的 #53 问题。 想要 新建一个分支并同时切换到那个分支上,你可以运行一个带有 -b 参数的 git
checkout 命令
git checkout -b iss53
相当于
git branch iss53
git checkout iss53
你继续在 #53 问题上工作,并且做了一些提交。 在此过程中,iss53 分支在不断的向前推进,因为你已经检出到该分支
$ git checkout -b iss53 #切回iss53分支
$ echo "iss53 50%" > iss53.txt #意味着iss53被提交了50%
现在你接到那个电话,有个紧急问题等待你来解决,有了 Git 的帮助,你不必把这个紧急问题和 iss53 的修改混在一起,你也不需要花大力气来还原关于 53# 问题的修改,然后再添加关于这个紧急问题的修改,最后将这个修改提交到线上分支。 你所要做的仅仅是切换回 master 分支,需要注意的是在你切换分支之前,保持好一个干净的状态。(提交你的所有修改)
#保证当前分支干净后再提交
git add ./
git commit -m "fourth commit for iss53 50%"
#切换回主分支
git checkout master
#为紧急问题创建一个新分支
git checkout -b hotbug
vim tesr.txt#在第三行加入内容"v3 to fix hotbug"模拟修复紧急bug
$ git commit -am "fiveth commit to fix hotbug"
此时的效果如下 产生了新的分支,因为我们在iss53分支未被合并和删除的时候又跳到了hotbug这个主线分支上,此时hotbug才是主线分支,master成了额外分支
分支合并 !!!你可以运行你的测试,确保你的修改是正确的,然后将其合并回你的 master 分支来部署到线上。 你可以使用 git merge 命令来达到上述目的
git checkout master
git merge hotfix
#提示如下
Updating 5cc0c2f..19aad0e #master由5cc0c2f版本跳到了19aad0e版本
Fast-forward #合并方式属于快进合并,没有任何冲突
#两个文件发生改动,改动类型都是插入新内容
iss53.txt | 1 +
test.txt | 1 +
2 files changed, 2 insertions(+)
#产生了一个新文件
create mode 100644 iss53.txt
在合并的时候,有时候会出现"快进(fast-forward)"这个词。 由于当前 master 分支所指向的提交是你当前提交的直接上游,所以 Git 只是简单的将指针向前移动。 换句话说,当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward) 例如:此时master分支的工作目录上,同时具有iss53.txt和最新的test.txt,此时我们过河拆桥,hotbug分支不再需要了额,直接删除
git branch -d hotbug
然后模拟处理iss53
git checkout iss53
vim a.txt
vim iss53.txt
合并
git checkout master
git merge iss53
#合并成功但伴随有以下提示
Auto-merging a.txt
CONFLICT (content): Merge conflict in a.txt
Automatic merge failed; fix conflicts and then commit the result.
也就是说a.txt出现了冲突
查看 也就是分支提交失败,git强制要求收到手动解决这个冲突后才能自动合并,这里很好解决,比较都是需要保留的,解决冲突可以再单独提交一份最终完整版本;
此时查看git lol 含义是master分支从188ded合并到34ad4bf时遇到了冲突,随后冲突被解决后形成一个新提交版本920b29c时被手动解决了这个冲突后被合并版本
所以我们发现,快进冲突其实就是将HEAD指针挪到最新的版本,前提是这两个分支在一条主线时,而传统冲突则是发生在不同主线上的分支合并时,此时需要手动解决冲突再提交成一个完整版本后再合并
总结:
- 分支的本质仅仅是指向提交对象的可变指向,指针的含义是指向某个提交对象,可变的含义是分支每次都指向当前分支上最新一次的提交对象
- 许多使用 Git 的开发者都喜欢使用这种方式来工作,比如只在 master 分支上保留完全稳定的代码——有可能仅仅是已经发布或即将发布的代码。 他们还有一些名为 develop 或者 next 的平行分支,被用来做后续开发或者测试稳定性——这些分支不必保持绝对稳定,但是一旦达到稳定状态,它们就可以被合并入 master 分支了。等待下一次的发布。随着你的提交而不断右移的指针。稳定分支的指针总是在提交历史中落后一大截,而前沿分支的指针往往比较靠前
git存储 首先我们知道切换分支前一定要保证当前分支是最干净的状态,但我们如果对当前工作目录做了修改但不想为此单独创建一个提交对象,直接切换的话git怕你修改内容丢失所以不允许你此时切换分支,会提示因为有已修改状态的文件所以导致切换失败;那这样的话我们
git stash #存储当前对象
# 含义是已经把工作目录状态工和索引状态WIP,保存(绑定)到了master分支920b29ccommit对象里了
Saved working directory and index state WIP on master: 920b29c fix conflict by hand
查看存储的内容
git stash list
tash@{0}: WIP on master: 920b29c fix conflict by hand
git后悔药 1、撤销工作目录
vim a.txt #修改暂存区文件
git status #此时会提醒Changes not staged for commit
git checkout -- a.txt #执行完这条命令,发现原理是把这个文件恢复到暂存区
注意,经过测试发现其原理是把当前文件恢复同步成暂存区的样子
2、 撤销暂存区的修改
vim a.txt
git add ./
撤销前查看暂存区
git ls-files -s
git cat-file -p 2125ca23ba8e3ce86171e5acb505a28a478b688a
撤销后再查看暂存区
git ls-files -s
git cat-file -p 2125ca23ba8e3ce86171e5acb505a28a478b688a
所以我们得出结论,此时暂存区中被修改过的文件会被修改为当前HEAD(HEAD和具体的分支所绑定)所指向commit对象
注意,这里只是修改了版本库,不涉及到工作区的修改,也就是工作区的a.txt文件还是保持原状
3 撤销版本库的修改
git commit –amend
这个命令会将暂存区中的文件提交。如果自上次提交以来你还未做任何修改(例如,在上次提交后马上执行了此命令),那么快照会保持不变,而你所修改的只是提交信息如果你提交后发现忘记了暂存某些需要的修改,可以像下面这样操作
- 未创建的分支。此时,只有工作目录有内容
* - 现在我们想要提交这个文件,所以用 git add 来获取工作目录中的内容,并将其复制到索引中
接着运行 git commit,它会取得索引中的内容并将它保存为一个永久的快照,然后创建一个指向该快照的提交对象,最后更新 master 来指向本次提交 此时如果我们运行 git status,会发现没有任何改动,因为现在三棵树完全相同现在我们想要对文件进行修改然后提交它。 我们将会经历同样的过程;首先在工作目录中修改文件。 我们称其为该文件的 v2 版本,并将它标记为红色 如果现在运行 git status,我们会看到文件显示在 “Changes not staged for commit,” 下面并被标记为红色,因为该条目在索引与工作目录之间存在不同。接着我们运行 git add 来将它暂存到索引中 此时,由于索引和 HEAD 不同,若运行 git status 的话就会看到“Changes to be committed” 下的该文件变为绿色 ——也就是说,现在预期的下一次提交与上一次提交不同。 最后,我们运行 git commit 来完成提交 reset 三部曲 - 移动 HEAD
reset 做的第一件事是移动 HEAD 的指向。假设我们再次修改了 file.txt 文件并第三次提交它。 现在的历史看起来是这样
git reset -soft HEAD~
#提示到
Unstaged changes after reset:
M file.txt
本质上是撤销了上次git commit命令,对比git checkout命令,该命令本质上是分支不变,即分支依然是指向到最新的提交对象,挪动的只是HEAD,HEAD指向新的分支,而上述命令是HEAD连带着分支一同回退到上一个commit对象,如下图 看一眼上图,理解一下发生的事情:它本质上是撤销了上一次 git commit 命令。 当你在运行 git commit 时,Git 会创建一个新的提交,并移动 HEAD 所指向的分支来使其指向该提交。当你将它 reset 回 HEAD~(HEAD 的父结点)时,其实就是把该分支移动回原来的位置,而不会改变索引和工作目录。 现在你可以更新索引并再次运行 git commit 来完成 git commit --amend 所要做的事情了。
案例,reset前 reset后 和上图是相互呼应的
git relog:HEAD有变化就会被git relog记录上
git log:打印当前主线分支的内容
git reset --soft 7a2299e
首先我们了解git reset这个命令只是用于回退提交对象版本,但一下命令
git reset --mixed HEAD~
#以下提示
Unstaged changes after reset:
M file.txt
查看工作区和暂存区
vim file.txt#查看工作区
#查看暂存区
git cat-file -p 03359d
git ls-files -s
可以得出结论,此命令在回退HEAD指向上一commit对象时,效果在git reset --soft的基础之上又多了一个回退暂存区的效果
3 先回退到第三次提交的版本上
git reset --mixed 7a2299e
HEAD指针和当前分支肯定都被切换到了7a2299e这个提交对象上了,我们再检查下他的暂存区,暂存区也响应被切换到了7a2299e所对应的提交对象,如果是则是 git reset --soft发现没有发生的变化,git reset -hard HEAD~则是同时动HEAD,分支,暂存区,工作目录
git checkout对比git reset --hard ${commitHash}
- 前者作用于HEAD、暂存区、工作目录,后者相同,工作目录需要注意第三点
- 指针角度:前者分支(指针)位置不会动,只是挪到HEAD(指针),后者是分支连带着HEAD一起动
- 数据安全性,同样工作目录存在未跟踪文件,前者切换分支时会通过污染其它分支的形式保留下未跟踪文件防止你未跟踪文件丢失,更不用说那些已修改状态,后者的话会强制把切换到指定版本的工作目录覆盖工作目录,这样的话未跟踪或者已修改状态的文件都会丢失数据
如果只是把暂存区一个文件(该文件还未被提交或者最近一次被修改后还未被提交)的版本往后回退,可以使用以下命令
git reset filename
如下所示,修改test.txt提交到暂存区但未提交,然后通过reset回退该文件到上一个test.txt版本 我们可知,的确是起到了撤销了暂存区的上一步提交,但实际上原理并非是提交而是起到覆盖的作用 原来是通过从HEAD指针中拿出commit对象中的包含这个文件的git对象覆盖住这个
补充: git commit --amend,这个命令会将暂存区中的文件提交。如果自上次提交以来你还未做任何修改(例如,在上次提交后马上执行了此命令),那么快照会保持不变,而你所修改的只是提交信息如果你提交后发现忘记了暂存某些需要的修改,可以像下面这样操作
git commit -m 'initial commit'
git add forgotten_file
git commit –amend
最终你只会有一个提交 - 第二次提交将代替第一次提交的结果 git reset 命令:git reset HEAD 文件名 作用:将文件从暂存区中撤回到工作目录
git checkout 命令:git checkout – 文件名 作用:将在工作目录中对文件的修改撤销
HEAD HEAD 是当前分支引用的指针,它总是指向该分支上的最后一次提交。这表示 HEAD 将是下一次提交的父结点。通常,理解 HEAD 的最简方式,就是将它看做 当前提交 的快照。
|