07_Git命令详解 - 后悔药

 教程目录

01_版本控制概述

02_Git概述

03_Git下载和安装

04_Git底层原理解析

05_Git命令详解 - CRUD

06_Git命令详解 - 分支

07_Git命令详解 - 后悔药

08_Git远程仓库

一、Git后悔药

撤销

撤销工作目录的修改

# 命令:
git restore 文件名 / git checkout 文件名

        作用:将在工作目录中对文件的修改撤销

         注意:git restore 文件名是一个危险的命令,这很重要。你对那个文件做的任何修改都会消失 -你只是拷贝了另一个文件来覆盖它。除非你确实清楚不想要那个文件了,否则不要使用这个命令。

撤销暂存区的修改

# 命令:
git restore --staged 文件名 / git reset HEAD 文件名

        作用:将文件从暂存区中撤回到工作目录

撤销提交

# 命令: 
git commit --amend

        作用:这个命令会将暂存区中的文件提交。如果自上次提交以来你还未做任何修改(例如,在上次提交后马上执行了此命令),那么快照会保持不变,而你所修改的只是提交信息。

         如果你提交后发现忘记了暂存某些需要的修改,可以像下面这样操作

git commit -m 'initial commit'

git add forgotten_file

git commit --amend

        最终你只会有一个提交 -第二次提交将代替第一次提交的结果

HEAD

         HEAD是当前分支引用的指针,它总是指向该分支上的最后一次提交。

        这表示 HEAD将是下一次提交的父结点。通常,理解HEAD的最简方式,就是将它看做当前提交的快照。

# 查看当前提交对象
git cat-file -p HEAD

# 查看当前提交对象对应的树对象的内容
git ls-tree -r HEAD

暂存区(索引区)

# 查看暂存区当前的样子
git ls-files -s

工作目录

        你可以把工作目录当做沙盒(沙箱)。在你将修改提交到暂存区并记录到历史之前,可以随意更改。

细化基本流程

        当我们运行git init,这会创建一个Git仓库,其中的HEAD引用指向未创建的分支。

        此时,只有工作目录有内容:

         现在我们想要提交这个文件,所以用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来完成提交。

         现在运行git status会没有输出,因为三棵树又变得相同了,切换分支或克隆的过程也类似。当检出一个分支时,它会修改HEAD指向新的分支引用,将索引填充为该次提交的快照,然后将索引的内容复制到工作目录中。

重置 reset

reset三部曲(commithash)

一、移动 HEAD

        reset做的第一件事是移动HEAD的指向。

        假设我们再次修改了file.txt文件并第三次提交它。现在的历史看起来是这样:

# 移动HEAD指向上一次提交对象
git reset --soft HEAD~

# 移动HEAD指向指定提交对象
git reset --soft 提交对象哈希

         这与改变 HEAD自身不同(checkout所做的);reset移动HEAD指向的分支。

        看一眼上图,理解一下发生的事情:它本质上是撤销了上一次 git commit命令。当你在运行git commit时,Git会创建一个新的提交,并移动 HEAD所指向的分支来使其指向该提交。

        当你将它reset回HEAD~(HEAD的父结点)时,其实就是把该分支移动回原来的位置,而不会改变索引和工作目录。现在你可以更新索引并再次运行git commit来完成git commit --amend所要做的事情了。

         回到默认版本:

第一部:git reset --soft HEAD~;
	只动HEAD(带着分支一起移动)

二、更新暂存区(索引)

         注意 git reset HEAD~等同于 git reset --mixed HEAD~

        理解一下发生的事情:它依然会撤销一上次提交,但还会取消暂存所有的东西。于是,我们回滚到了所有 git add和 git commit的命令执行之前。

# 如果通过--mixed命令到该版本,如果还想回去v3,必须也通过--mixed回去
git reset --mixed v3哈希
第二部:git reset --mixed HEAD~;
	动HEAD(带着分支一起移动)
	动暂存区

三、更新工作目录

         你撤销了最后的提交、git add和git commit命令以及工作目录中的所有工作。

第三部:git reset --hard HEAD~;
	动HEAD(带着分支一起移动)
	动暂存区
	动工作目录

注意点

        必须注意,--hard标记是reset命令唯一的危险用法,它也是 Git会真正地销毁数据的仅有的几个操作之一。其他任何形式的reset调用都可以轻松撤消,但是--hard选项不能,因为它强制覆盖了工作目录中的文件。

        在这种特殊情况下,我们的 Git数据库中的一个提交内还留有该文件的 v3版本,我们可以通过reflog来找回它。但是若该文件还未提交,Git仍会覆盖它从而导致无法恢复。

路径 reset

        前面讲述了 reset基本形式的行为,不过你还可以给它提供一个作用路径。若指定了一个路径,reset将会跳过第 1步,并且将它的作用范围限定为指定的文件或文件集合。这样做自然有它的道理,因为 HEAD只是一个指针,你无法让它同时指向两个提交中各自的一部分。不过索引和工作目录可以部分更新,所以重置会继续进行第 2、3步。

        现在,假如我们运行 git reset file.txt(这其实是 git reset --mixed HEAD file.txt的简写形式,),它会:移动 HEAD分支的指向(因为是文件这一步忽略)让索引看起来像 HEAD所以它本质上只是将 file.txt从 HEAD复制到索引中

checkout

一、不带路径

        git checkout [branch]

        运行 git checkout [branch]与运行 git reset --hard [branch]非常相似,它会更新三者使其看起来像[branch],不过有两点重要的区别:

        首先不同于 reset --hardcheckout对工作目录是安全的,它会通过检查来确保不会将已更改的文件弄丢。而 reset --hard则会不做检查就全面地替换所有东西。

        第二个重要的区别是如何更新 HEAD。reset会移动 HEAD分支的指向,而checkout只会移动 HEAD自身来指向另一个分支。

        例如,假设我们有master和develop分支,它们分别指向不同的提交;我们现在在develop上。如果我们运行git reset master,那么develop自身现在会和master指向同一个提交。而如果我们运行git checkout master的话,develop不会移动,HEAD自身会移动。现在HEAD将会指向master。

        所以,虽然在这两种情况下我们都移动 HEAD使其指向了提交 A,但做法是非常不同的。reset会移动 HEAD分支的指向,而checkout则移动 HEAD自身。

二、带路径

        git checkout <file>

        运行 checkout的另一种方式就是指定一个文件路径,这会像 reset一样不会移动 HEAD。它就像是git reset --hard [branch] file。这样对工作目录并不安全,它也不会移动 HEAD将会跳过第 1步更新暂存区和工作目录

        git checkout <file> 相比于 git reset hard commitHash跟文件名的形式第一第二步都没做

log与reflog的比较

        用git命令,想看到自己的操作记录,则可以使用log与reflog,它两个的区别如下:

                1、git log 命令可以显示所有提交过的版本信息

                2、git reflog 可以查看所有分支的所有操作记录(包括已经被删除的 commit 记录和 reset 的操作)

二、数据恢复

        在你使用 Git的时候,你可能会意外丢失一次提交。通常这是因为你强制删除了正在工作的分支,但是最后却发现你还需要这个分支;亦或者硬重置了一个分支,放弃了你想要的提交。如果这些事情已经发生,该如何找回你的提交呢?

实例

        假设你已经提交了五次

$ git log --pretty=oneline

ab1afef80fac8e34258ff41fc1b867c702daa24b	modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a	added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9	third commit
cac0cab538b970a37ea1e769cbbde608743bc96d	second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d	first commit

        现在,我们将master分支硬重置到第三次提

$ git reset --hard 1a410efbd13591db07496601ebc7a059dd55cfe9

HEAD is now at 1a410ef third commit

$ git log --pretty=oneline

1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

        现在顶部的两个提交已经丢失了 -没有分支指向这些提交。你需要找出最后一次提交的 SHA-1然后增加一个指向它的分支。窍门就是找到最后一次的提交的 SHA-1 -但是估计你记不起来了,对吗?

        最方便,也是最常用的方法,是使用一个名叫git reflog的工具。当你正在工作时,Git会默默地记录每一次你改变 HEAD时它的值。每一次你提交或改变分支,引用日志都会被更新;

$ git reflog
1a410ef HEAD@{0}: reset: moving to 1a410ef
ab1afef HEAD@{1}: commit: modified repo.rb a bit
484a592 HEAD@{2}: commit: added repo.rb
。。。

        git reflog并不能显示足够多的信息。为了使显示的信息更加有用,我们可以执行git log -g,这个命令会以标准日志的格式输出引用日志

恢复

        看起来下面的那个就是你丢失的提交,你可以通过创建一个新的分支指向这个提交来恢复它。例如,你可以创建一个名为recover-branch的分支指向这个提交(ab1afef)

git branch recover-branch ab1afef

        现在有一个名为 recover-branch的分支是你的 master分支曾经指向的地方,再一次使得前两次提交可到达了。

三、打tag

        Git可以给历史中的某一个提交打上标签,以示重要。比较有代表性的是人们会使用这个功能来标记发布结点(v1.0等等)。

列出标签

git tag

git tag -l 'v1.8.5*'

v1.8.5 v1.8.5-rc0 v1.8.5-rc1 v1.8.5-rc2 v1.8.5-rc3 v1.8.5.1 v1.8.5.2 v1.8.5.3

创建标签

        Git使用两种主要类型的标签:轻量标签与附注标签

        轻量标签很像一个不会改变的分支 -它只是一个特定提交的引用

git tag v1.4

git tag v1.4 commitHash

        附注标签是存储在 Git数据库中的一个完整对象。它们是可以被校验的;其中包含打标签者的名字、电子邮件地址、日期时间;还有一个标签信息;通常建议创建附注标签,这样你可以拥有以上所有信息;但是如果你只是想用一个临时的标签,或者因为某些原因不想要保存那些信息,轻量标签也是可用的

git tag -a v1.4
git tag -a v1.4 commitHash
git tag -a v1.4 commitHash -m 'my version 1.4'

查看特定标签

        git show可以显示任意类型的对象(git对象、树对象、提交对象、tag对象)

git show [tagname]

        如果想要一次性推送很多标签,也可以使用带有 --tags选项的 git push命令。这将会把所有不在远程仓库服务器上的标签全部传送到那里。

git push origin --tags

删除标签

        删除标签要删除掉你本地仓库上的标签,可以使用命令 git tag -d <tagname>。

        例如,可以使用下面的命令删除掉一个轻量级标签:

git tag -d v1.4

        应该注意的是上述命令并不会从任何远程仓库中移除这个标签,你必须使用git push <remote> :refs/tags/<tagname>来更新你的远程仓库:

git push origin :refs/tags/v1.4

检出标签

        如果你想查看某个标签所指向的文件版本,可以使用 git checkout命令

git checkout tagname

        虽然说这会使你的仓库处于“分离头指针(detacthed HEAD)”状态。在“分离头指针”状态下,如果你做了某些更改然后提交它们,标签不会发生变化,但你的新提交将不属于任何分支,并且将无法访问,除非访问确切的提交哈希。因此,如果你需要进行更改——比如说你正在修复旧版本的错误——这通常需要创建一个新分支:

git checkout -b version2

四、Git特点

        在开始学习Git的时候,请不要尝试把各种概念和其他版本控制系统(诸如Subversion和 Perforce等)相比拟,否则容易混淆每个操作的实际意义。Git在保存和处理各种信息的时候,虽然操作起来的命令形式非常相近,但它与其他版本控制系统的做法颇为不同。理解这些差异将有助于你准确地使用 Git提供的各种工具。

        直接记录快照,而非差异比较

        Git和其他版本控制系统的主要差别在于,Git只关心文件数据的整体是否发生变化,而大多数其他系统则只关心文件内容的具体差异。这类系统(CVS,Subversion,Perforce,Bazaar等等)每次记录有哪些文件作了更新,以及都更新了哪些行的什么内容. (下图)其他系统在每个版本中记录着各个文件的具体差异

         Git并不保存这些前后变化的差异数据。实际上, Git更像是把变化的文件作快照后,记录在一个微型的文件系统中。每次提交更新时,它会纵览一遍所有文件的指纹信息并对文件作一快照,然后保存一个指向这次快照的索引。为提高性能,若文件没有变化,Git不会再次保存,而只对上次保存的快照作一链接。

        Git的工作方式就像下图所示(保存每次更新时的文件快照):

         这是 Git同其他系统的重要区别。它完全颠覆了传统版本控制的套路,并对各个环节的实现方式作了新的设计。Git更像是个小型的文件系统,但它同时还提供了许多以此为基础的超强工具,而不只是一个简单的 VCS。

近乎所有操作都是本地执行

        在 Git中的绝大多数操作都只需要访问本地文件和资源,不用连网。但如果用 CVCS的话,差不多所有操作都需要连接网络。因为 Git 在本地磁盘上 就保存着所有当前项目的历史更新,所以处理起来速度飞快。

时刻保持数据完整性

        在保存到 Git之前,所有数据都要进行内容的校验和计算,并将此结果作为数据的唯一标识和索引。换句话说,不可能在你修改了文件或目录之后,Git一无所知。这项特性作为 Git的设计哲学,建在整体架构的最底层。所以如果文件在传输时变得不完整,或者磁盘损坏导致文件数据缺失,Git都能立即察觉。Git使用 SHA-1算法计算数据的校验,通过对文件的内容或目录的结构计算出一个 SHA-1哈希值,作为指纹字符串。该字串由 40个十六进制字符(0-9及 a-f)组成,看起来就像是:24b9da6552252987aa493b52f8696cd6d3b00373

        Git 的工作完全依赖于这类指纹字串,所以你会经常看到这样的哈希值。 实际上,所有保存在 Git 数据库中的东西都是用此哈希值来作索引的,而不 是靠文件名。

多数操作仅添加数据

        常用的 Git 操作大多仅仅是把数据添加到数据库。因为任何一种不可逆的操作,比如删除数据,都会使回退或重现历史版本变得困难重重。在别的VCS 中,若还未提交更新,就有可能丢失或者混淆一些修改的内容,但在 Git 里,一旦提交快照之后就完全不用担心丢失数据,特别是养成定期推送到其他仓库的习惯的话。

        这种高可靠性令我们的开发工作安心不少,尽管去做各种试验性的尝试好了,再怎样也不会弄丢数据。

文件的三种状态

        对于任何一个文件,在 Git 内都只有三种状态(Git 外的状态就是一个普通文件):

        已提交(committed),已提交表示该文件已经被安全地保存在本地数据库中了;

        已修改(modified) , 已修改表示修改了某个文件,但还没有提交保存;

        已暂存(staged) , 已暂存表示把已修改的文件放在下次提交时要保存的清单中

        由此我们看到 Git 管理项目时,文件流转的三个工作区域:

        Git 的工作目录,暂存区域,本地仓库!!!!

五、Git工作流

         每个项目都有一个 Git 目录(.git )它是 Git 用来保存元数据和对象数据库的地方。该目录非常重要,每次克隆镜像仓库的时候,实际拷贝的就是这个目录里面的数据。

1、在工作目录中修改某些文件。

        a) 从项目中取出某个版本的所有文件和目录,用以开始后续工作的叫做工作 目录。这些文件实际上都是从 Git 目录中的压缩对象数据库中提取出来的,接下来就可以在工作目录中对这些文件进行编辑。

2、保存到暂存区域,对暂存区做快照

        a) 暂存区域只不过是个简单的文件,一般都放在 Git 目录中。有时候人们 会把这个文件叫做索引文件,不过标准说法还是叫暂存区域。

3、提交更新,将保存在暂存区域的文件快照永久转储到本地数据库(Git 目录) 中

        我们可以从文件所处的位置来判断状态:如果是 Git 目录中保存着的特定版 本文件,就属于已提交状态;如果作了修改并已放入暂存区域,就属于已暂存 状态;如果自上次取出后,作了修改但还没有放到暂存区域,就是已修改状态。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是波哩个波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值