第一篇《精通Git–基础》已经对 Git 的基本使用有了了解,接下来这一篇来看一下平时工作中会遇到的一些比较棘手的问题。
GitHub 操作演示
远程 git 仓库
在 GitHub 创建仓库有两种方式,一种是当前没有仓库,我们需要新建一个 git 仓库:
还有一种情况,我们事先已经有了一个仓库,需要迁移过来,就可以使用 GitHub 的迁移功能:
本地 git 仓库
当我们远程 Git 仓库创建好后,有两种情况:
第一种情况,本地什么代码都没有,需要新建仓库,新建一个文件夹,例如我们叫 myGitWork,然后进入该目录执行如下命令:
在此之前你也可以添加一些文件,比如 READEME.md
, 添加一些内容:
|
|
|
|
此时我们可能不太理解这个 git push -u
中的 -u
参数,使用 git help push
命令查看帮助:
这个 -u
参数在推送本地文件到远程仓库 origin 的同时,指定了 origin 为默认远程主机,后面就不用再加参数,直接使用 git push
命令了。
还有一种情况,我们本地已有仓库,此时只需要给添加远程仓库地址即可:
|
|
版本回退
工作区回退
假设我们此时我们修改了 README.md
文件,新增了一句话:
|
|
如果我们要撤销,很简单,执行 git checkout -- <file>
:
|
|
这个命令会撤销所有你对该文件的修改,回到本地仓库中的文件状态。
暂存区回退
假设上面的 README.md
文件已经修改,而且我们执行了 git add .
将该文件添加到了暂存区:
|
|
我们如果要回退到工作区,则使用上面建议的 git reset HEAD <file>
命令即可:
|
|
本地仓库回退
假设我们上面的 README.md
文件的修改我们提交到了本地仓库 git commit -m "change"
,我们如何回退到之前的版本呢。
首先,我们使用 git log
查看一下最近提交的信息:
|
|
接下来使用命令 git reset --hard
来回退到某个版本(使用 commit 的 id 前几位即可):
|
|
此时就强制回退到了上次提交的状态。假设我们还想让上一个版本的修改存在于暂存区怎么办?
|
|
远程仓库回退
实际上远程仓库回退和本地仓库回退是一样的,因为都要通过本地回退,然后 push 到远程仓库,但是 push 的时候会出错:
|
|
此时要使用 git push origin HEAD --force
推到远程仓库:
|
|
回退的原理
回顾一下上一篇提到的分支部分的示意图, 我们已经知道每一次提交都会产生一个新对象(commit 对象)指向我们的文件新快照,而 HEAD 游标指向的分支(下图的 master 和 testing 是两个分支)会指向这个新的对象,假设当前分支是 master 则 master 分支会向前移动指向新的 commit 对象。
HEAD 是当前分支引用的指针,它总是指向该分支上的最后一次提交。 这表示 HEAD 将是下一次提交的父结点。 通常,理解 HEAD 的最简方式,就是将它看做 你的上一次提交 的快照。
查看 HEAD 快照:
|
|
除了上面的 HEAD 指针外,还有一个 索引 的概念,索引是你的 预期的下一次提交。 我们也会将这个概念引用为 Git 的 暂存区域
,这就是当你运行 git commit 时 Git 看起来的样子。
我们前面提到的文件快照(文件的变化)都保存在 .git
文件夹中,工作区,暂存区,HEAD游标的关系如图:
你可以这样理解,上图中的文件的三个状态都对应着一个文件快照树,我们的状态改变只不过是这个索引的改变而已,或者说是指针的移动而已。
接下来我们来以指针角度分析一下仓库文件变化的过程:
|
|
此时 HEAD 和 master (有可能 master 还不存在) 都处于游离状态,没有固定指向。假设我们现在放入一个文件:
|
|
此时处于工作区,和暂存区(索引),本地仓库(HEAD)没有任何的关系:
|
|
此时暂存区的索引指向了此次 git add
的文件快照树:
|
|
这个时候 HEAD 索引指向的分支(此时是 master 分支)指向暂存区索引的文件快照树:
接下来修改文件内容:
|
|
此时指向 git status
会检查工作区的文件快照和暂存区的文件快照是否一致,不一致就会显示 modified: README.md
此时再执行 git add README.md
暂存区的索引就会指向工作区新的文件快照树的根。
接下来我们执行 git reset
命令:
|
|
reset(重置) 命令的本质是移动 HEAD 所指向的分支的指针,上面的 git reset HEAD
等价于 git reset --mixed HEAD
, 它会把暂存区清空,并把原节点和 reset 节点的差异的文件放在工作目录,总而言之就是,工作目录的修改、暂存区的内容以及由 reset 所导致的新的文件差异,都会被放进工作目录。
我们上面尝试 git reset --hard
来回退本地仓库的代码:
|
|
这里的 --hard
参数的意思是重置 HEAD 和指向的分支指针位置,同时重置暂存区和工作目录的内容。所以你使用 git status
会看到仓库回退后暂存区和工作区都清空了。
如果我们使用 --soft
参数就可以保留暂存区,并把重置 HEAD 所带来的新的差异放进暂存区,而且会保留工作区内容, 下方示意图的 4 和 3 的差异:
|
|
这里我们已经大概能总结出 git reset
的一些特点了:
操作仓库(commit 状态)
- 如果加参数
git reset --hard <commitId> <file>
会清空工作目录和暂存区的改动,回退到上一个 commit. - 如果加参数
git reset --soft <commitId> <file>
会保留工作目录和回退的 commit 版本与 HEAD 指向的 commit 版本的差异保留在暂存区。 - 如果不加参数
git reset <commitId> <file>
默认是--mixed
, 则会保留工作区,清空暂存区,将会把所有差异放入工作区。
操作暂存区(stage 状态)
- 如果不加参数
git reset HEAD <file>
等效于git reset --mixed HEAD <file>
是重置暂存区,并把差异放入工作区。