目录
[创建 Git 本地仓库](#创建 Git 本地仓库)
[配置 Git](#配置 Git)
[查看 .git 文件](#查看 .git 文件)
[情况二:已经add ,但没有commit](#情况二:已经add ,但没有commit)
[情况三:已经add ,并且也commit 了](#情况三:已经add ,并且也commit 了)
创建 Git 本地仓库
要提前说的是,仓库是进行版本控制的一个文件目录。我们要想对文件进行版本控制,就必须先创建一个仓库出来。
创建一个 Git 本地仓库对应的命令为 git init ,注意命令要在已经存在的文件目录下执行,之后这个文件目录就成了本地仓库,例如:创建gitcode目录,在gitcode目录下执行git init,gitcode就变成了本地仓库
hyb@139-159-150-152:~/gitcode$ pwd
/home/hyb/gitcode
hyb@139-159-150-152:~/gitcode$ git init
Initialized empty Git repository in /home/hyb/gitcode/.git/
hyb@139-159-150-152:~/gitcode$ ll -a
total 12
drwxrwxr-x 3 hyb hyb 4096 May 5 15:49 ./
drwxr-xr-x 13 hyb hyb 4096 May 5 15:47 ../
drwxrwxr-x 7 hyb hyb 4096 May 5 15:49 .git/
我们发现,当前目录下多了一个.git 的隐藏文件, .git 目录是 Git 来跟踪管理仓库的,不要手动
修改这个目录里面的文件,不然改乱了,就把 Git 仓库给破坏了。
其中包含 Git 仓库的诸多细节,有兴趣的可以进入看看。
cpp
hyb@139-159-150-152:~/gitcode$ tree .git/
.git/
├── branches
├── config
├── description
├── HEAD
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── fsmonitor-watchman.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── pre-merge-commit.sample
│ ├── prepare-commit-msg.sample
│ ├── pre-push.sample
│ ├── pre-rebase.sample
│ ├── pre-receive.sample
│ └── update.sample
├── info
│ └── exclude
├── objects
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
9 directories, 16 files
配置 Git
当安装 Git 后首先要做的事情是设置你的 用户名称 和 e-mail 地址,这是非常重要的。配置命令为:
cpp
git config [--global] user.name "Your Name"
git config [--global] user.email "email@example.com"
# 把 Your Name 改成你的昵称
# 把 email@example.com 改成邮箱的格式,只要格式正确即可。
其中**--global 是一个可选项。如果使用了该选项,表示这台机器上所有的 Git 仓库都会使用** 这个配置。如果你希望在不同仓库中使用不同的 name 或 e-mail ,可以不要 --global 选项,但要注意的是,执行命令时必须要在仓库里。
查看配置命令为:
cpp
git config -l
删除对应的配置命令为:
cpp
git config [--global] --unset user.name
git config [--global] --unset user.email
下图配置完后进行查看,可看到配置完成,其他的信息暂时可以不用管

下面是使用--global全局选项,需要注意的是使用全部选项配置会后,想要重新删除配置,也必须使用--global

认识工作区、暂存区、版本库
• 工作区 :是在电脑上你要写代码或文件的目录,即你本地仓库下除了.git之外的区域。
• 暂存区 :英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,我们把暂存区有时也叫作索引(index)。
• 版本库 :又名仓库,英文名 repository 。工作区有一个隐藏目录 .git ,它不算工作区,而
是 Git 的版本库 。这个版本库里面的所有文件都可以被 Git 管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以"还原"。
下面这个图展示了工作区、暂存区和版本库之间的关系:

• 图中左侧为工作区,右侧为版本库 。Git 的版本库里存了很多东西,其中最重要的就是暂存区。
• 在创建 Git 版本库时,Git 会为我们自动创建一个唯一的 master 分支,以及指向 master 的一个指针叫 HEAD。(分支和HEAD的概念后面再说)
•Git本身追踪管理的并不是文件,而是修改,对文件的每次修改除了文件本身,会形成一个新的object在objects区域内。
•工作区、暂存区、索引区内部都是通过树结构存储的 。在工作区,每次我们在工作区进行修改(修改包括对文件的新增、删除、修改)后的结果都是作为对象进行保存的 ,而版本库中有一块objects的区域专门存储这些对象,暂存区并不存储具体的对象,而是存储结果objects的索引 ,当对工作区修改(或新增)的文件执行 git add 命令时,会将修改的结果推送给暂存区,,暂存区目录树的文件索引会被更新,存储指向objects区域内新对象的索引。•master内目录树存储的同样是指向objects的索引,当执行提交操作git commit 时,master 分支会做相应的更新,可以简单理解为暂存区的目录树才会被真正写到版本库中。
由上述描述我们便能得知:通过新建或粘贴进目录的文件,并不能称之为向仓库中新增文件,而只是在工作区新增了文件。必须要通过使用 git add 和 git commit 命令才能将文件添加到仓库中进行版本管理。
添加文件
场景一
在包含 .git 的目录下新建一个 ReadMe 文件,我们可以使用 git add 命令可以将文件添加到暂存区:
• 添加一个或多个文件到暂存区: git add [file1] [file2] ...
• 添加指定目录到暂存区,包括子目录: git add [dir]
• 添加当前目录下的所有文件改动到暂存区: git add .
再使用 git commit 命令将暂存区内容添加到本地仓库中:
• 提交暂存区全部内容到本地仓库中: git commit -m "message"
• 提交暂存区的指定文件到仓库区: git commit [file1] [file2] ... -m "message"
注意git commit 后面的-m 选项,就是用来进行添加备注的,对本次修改的说明,描述本次提交的 message ,由用户自己完成,这部分内容绝对不能省略,并要好好描述,是用来记录你的提交细节,是给我们人看的,可以帮助我们更好理解每次操作的具体细节,有助于我们恢复到想要的版本,当迭代非常多次后想再恢复,这一点非常重要。
例如:
cpp
hyb@139-159-150-152:~/gitcode$ vim ReadMe
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello bit
hyb@139-159-150-152:~/gitcode$ git add ReadMe
hyb@139-159-150-152:~/gitcode$ git commit -m "commit my first file"
[master (root-commit) c614289] commit my first file
1 file changed, 2 insertions(+)
create mode 100644 ReadMe
git commit 命令执行成功后会告诉我们,1个文件被改动(就是我们新添加的ReadMe文件),插
入了两行内容(ReadMe有两行内容)。
我们还可以多次 add 不同的文件,而只 commit 一次便可以提交所有文件,是因为需要提交的文件是通通被 add 到暂存区中,然后一次性 commit 暂存区的所有修改。如:
cpp
hyb@139-159-150-152:~/gitcode$ touch file1 file2 file3
hyb@139-159-150-152:~/gitcode$ git add file1
hyb@139-159-150-152:~/gitcode$ git add file2
hyb@139-159-150-152:~/gitcode$ git add file3
hyb@139-159-150-152:~/gitcode$ git commit -m "add 3 files"
[master 23807c5] add 3 files
3 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 file1
create mode 100644 file2
create mode 100644 file3
截至目前为止,我们已经能够将代码直接提交至本地仓库了。我们可以使用 git log 命令,来查看
下历史提交记录:
cpp
hyb@139-159-150-152:~/gitcode$ git log
commit 23807c536969cd886c4fb624b997ca575756eed6 (HEAD -> master)
Author: hyb91 <2689241679@qq.com>
Date: Sat May 6 11:27:32 2023 +0800
add 3 files
commit c61428926f3853d4ec6dde904415b0e6c1dabcc6
Author: hyb91 <2689241679@qq.com>
Date: Sat May 6 11:25:50 2023 +0800
commit my first file
该命令显示从最近到最远的提交日志,并且可以看到我们 commit 时的日志消息。
如果嫌输出信息太多,看得眼花缭乱的,可以试试加上**--pretty=oneline 参数,这个选项可以进行美化,将信息一行输出,**接下来我们就能看到我们之前所做的操作都被记录来了,并且有一串字符串作为标识与之对应
cpp
hyb@139-159-150-152:~/gitcode$ git log --pretty=oneline
23807c536969cd886c4fb624b997ca575756eed6 (HEAD -> master) add 3 files
c61428926f3853d4ec6dde904415b0e6c1dabcc6 commit my first file
需要说明的是,我们看到的一大串类似23807c5...56eed6 的是每次提交的commit id (版本
号) ,Git 的commit id 不是1,2,3......递增的数字,而是一个 SHA1 计算出来的一个非常大的数字,用十六进制表示(你看到的commit id 和我的肯定不一样,以你自己的为准)。
查看 .git 文件
先来看看我们的.git 的目录结构:
cpp
hyb@139-159-150-152:~/gitcode$ tree .git/
.git/
├── branches
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── fsmonitor-watchman.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── pre-merge-commit.sample
│ ├── prepare-commit-msg.sample
│ ├── pre-push.sample
│ ├── pre-rebase.sample
│ ├── pre-receive.sample
│ └── update.sample
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── 23
│ │ └── 807c536969cd886c4fb624b997ca575756eed6
│ ├── 83
│ │ └── 0a8c9feefbdc098bbae2cdc25e5034ce1920d7
│ ├── 8f
│ │ └── add50161b6fafa53ce7e79d278dc490240c946
│ ├── 9c
│ │ └── 9e1f0f6bff3015df71a0963004476f5e6cfd54
│ ├── c6
│ │ └── 1428926f3853d4ec6dde904415b0e6c1dabcc6
│ ├── e6
│ │ └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
│ ├── info
│ └── pack
└── refs
├── heads
│ └── master
└── tags
18 directories, 27 files
1. index 就是我们的暂存区,add 后的内容都是添加到这里的。
2. HEAD 就是我们的默认指向 master 分支的指针:
cpp
hyb@139-159-150-152:~/gitcode$ cat .git/HEAD
ref: refs/heads/master
而默认的 master 分支,其实就是:
cpp
hyb@139-159-150-152:~/gitcode$ cat .git/refs/heads/master
23807c536969cd886c4fb624b997ca575756eed6
打印的23807c536969cd886c4fb624b997ca575756eed6 是什么东西呢?我们发现master存储的就是当前最新版本的commit id 。
objects 为 Git 的对象库,里面包含了修改过程中创建的各种版本库对象及内容 。当执行 git add 命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,就位于 ".git/objects" 目录下,
让我们来看看这些对象有何用处:
cpp
hyb@139-159-150-152:~/gitcode$ ls .git/objects/
23 83 8f 9c c6 e6 info pack
查找 object 时要将commit id 分成2部分,其前2位是文件夹名称,后38位是文件名称 。
找到这个文件之后,一般不能直接看到里面是什么,该类文件是经过 sha (安全哈希算法)加密过的文件 ,好在我们可以使用git cat-file 命令来查看版本库对象的内容:
cpp
hyb@139-159-150-152:~/gitcode$ git cat-file -p 23807c536969cd886c4fb624b997ca575756eed6
tree 830a8c9feefbdc098bbae2cdc25e5034ce1920d7
parent c61428926f3853d4ec6dde904415b0e6c1dabcc6
author hyb91 <2689241679@qq.com> 1683343652 +0800
committer hyb91 <2689241679@qq.com> 1683343652 +0800
add 3 files
# 我们发现这就是我们最近一次提交的相关描述信息
# parent发现存储的是上一次提交的commit id
其中,还有一行 tree 830a8c9feefbdc098bbae2cdc25e5034ce1920d7 ,我们使用同样的方
法,看看结果:
cpp
hyb@139-159-150-152:~/gitcode$ git cat-file -p 830a8c9feefbdc098bbae2cdc25e5034ce1920d7
100644 blob 9c9e1f0f6bff3015df71a0963004476f5e6cfd54 ReadMe
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file1
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file2
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file3
再看 ReadMe 对应的 9c9e1f0f6bff3015df71a0963004476f5e6cfd54 :
cpp
hyb@139-159-150-152:~/gitcode$ git cat-file -p
9c9e1f0f6bff3015df71a0963004476f5e6cfd54
hello bit
hello bit
# 这是我们对ReadMe做的修改!!被git记录了下来!!
所以我们发现我们对每一个文件所做的每一次操作所形成的版本都被git记录追踪了,并有唯一的commit id来标识
总结一下,在本地的 git 仓库中,有几个文件或者目录很特殊
•index: 暂存区, git add 后会更新该内容。
• HEAD: 默认指向 master 分支的一个指针。
•refs/heads/master: 文件里保存当前 master 分支的最新commit id。
• objects: 包含了创建的各种版本库对象及内容,可以简单理解为存放了 git 维护的所有修改。
后面再学习过程中,最好能将常见的git 操作与.git 目录当中的结构内容变化对应起来,这样有利于我们理解git 细节流程。
场景二
学习到这里,我们已经清楚了如何向仓库中添加文件,并且对于工作区、暂存区、版本库也有了一定的认识。那么我们再展示一种添加文件的场景,能加深对工作区、暂存区、版本库的理解,示例如下:
cpp
hyb@139-159-150-152:~/gitcode$ touch file4 #1. 新增file4文件
hyb@139-159-150-152:~/gitcode$ git add file4 #2. 将file4添加到暂存区
hyb@139-159-150-152:~/gitcode$ touch file5 #3. 新增file5文件
hyb@139-159-150-152:~/gitcode$ git commit -m"add file" #4. 提交修改
[master 3d406c0] add file
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 file4
提交后发现打印了 1 file changed, 0 insertions(+), 0 deletions(-) ,意思是只有一个文件改变了,这时我们提出了疑问,不是新增了两个文件吗?再来回忆下, git add 是将文件添加到暂存区, git commit 是将暂存区的内容添加到本地仓库中。由于我们并没有使用 git add file5 ,file5 就不在暂存区中维护,所以我们 commit 的时候其实只是把已经在暂存区的 file4 提交了,而遗漏了工作区的 file5。如何提交 file5 呢?很简单,再次add , commit 即可。
修改文件
Git 比其他版本控制系统设计得优秀,因为 Git 跟踪并管理的是修改,而非文件。
什么是修改?比如你新增了一行,这就是一个修改,删除了一行,也是一个修改,更改了某些字符,也是一个修改,删了一些又加了一些,也是一个修改,甚至创建一个新文件,也算一个修改。
让我们将 ReadMe 文件进行一次修改:
cpp
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
此时,仓库中的 ReadMe 和我们工作区的 ReadMe 是不同的,如何查看当前仓库的状态呢?git
status 命令用于查看在你上次提交之后是否有对文件进行再次修改。
cpp
hyb@139-159-150-152:~/gitcode$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: ReadMe
no changes added to commit (use "git add" and/or "git commit -a")
上面的结果告诉我们,ReadMe 被修改过了,但还没有完成添加与提交。
目前,我们只知道文件被修改了,如果能知道具体哪些地方被修改了,就更好了。这里需要注意的是虽然这里我们所使用的例子简单,使用者直接指导哪里修改,但在实际开发中,我们面对的可能是几天、几个月前的修改,所以不能单纯靠人的记忆。
git diff [file] 命令用来显示暂存区和工作区文件的差异,显示的格式正是Unix通用的diff格
式。也可以使用 git diff HEAD -- [file] 命令来查看版本库和工作区文件的区别 。
知道了对 ReadMe 做了什么修改后,再把它提交到本地仓库就放心多了。
cpp
hyb@139-159-150-152:~/gitcode$ git diff ReadMe
diff --git a/ReadMe b/ReadMe
index 9c9e1f0..4a97140 100644
--- a/ReadMe #---、a都表示改动前,+++、bbb都表示改动后
+++ b/ReadMe
@@ -1,2 +1,3 @@ #"-1"中的-表示改动前,1表示文件第一,后面的2表示从第1行开始有两行
hello bit #"+1"中的+表示改动后,1表示第一行,后面的3表示从第1行开始有三行
-hello bit #-表示删除的内容
+hello git #+表示新增的内容
+hello world
cpp
hyb@139-159-150-152:~/gitcode$ git add ReadMe
hyb@139-159-150-152:~/gitcode$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: ReadMe
git add 之后,就没有看到上面 no changes added to commit (use "git add"
and/or "git commit -a") 的消息了。接下来让我们继续 git commit 即可:
cpp
hyb@139-159-150-152:~/gitcode$ git commit -m "add modify ReadMe file"
[master 94da695] add modify ReadMe file
1 file changed, 2 insertions(+), 1 deletion(-)
hyb@139-159-150-152:~/gitcode$ git status
On branch master
nothing to commit, working tree clean
版本回退
之前我们也提到过,Git 能够管理文件的历史版本,这也是版本控制器重要的能力。如果有一天你发现之前的工作做的出现了很大的问题,需要在某个特定的历史版本重新开始,这个时候,就需要版本回退的功能了。
执行 git reset 命令用于回退版本,可以指定退回某一次提交的版本。要解释一下"回退"本质是要将版本库中的内容进行回退,工作区或暂存区是否回退由命令参数决定:
git reset 命令语法格式为: git reset [--soft | --mixed | --hard] [HEAD]
• --soft 参数对于工作区和暂存区的内容都不变,只是将版本库回退到某个指定版本。
• --mixed 为默认选项,使用时可以不用带该参数。该参数将暂存区的内容退回为指定提交版本内容,工作区文件保持不变。
• --hard 参数将所有区域都退回到指定版本。切记工作区有未提交的代码时不要用这个命令,因为工作区会回滚,你没有提交的代码就再也找不回了,所以使用该参数前一定要慎重。

git reset [--soft | --mixed | --hard] [HEAD]
• HEAD 说明:
◦ 可直接写成 commit id,表示指定退回的版本
◦ HEAD 表示当前版本
◦ HEAD^ 上一个版本
◦ HEAD^^ 上上一个版本
◦ 以此类推...
• 可以使用 ~数字表示:
◦ HEAD~0 表示当前版本
◦ HEAD~1 上一个版本
◦ HEAD^2 上上一个版本
◦ 以此类推...
为了便于表述,方便测试回退功能,我们先做一些准备工作:更新3个版本的 ReadMe,并分别进行3次提交,如下所示:
cpp
# 第一次修改提交
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hyb@139-159-150-152:~/gitcode$ git add ReadMe
hyb@139-159-150-152:~/gitcode$ git commit -m"add version1"
[master cff9d1e] add version1
1 file changed, 1 insertion(+)
# 第二次修改提交
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hyb@139-159-150-152:~/gitcode$ git add ReadMe
hyb@139-159-150-152:~/gitcode$ git commit -m"add version2"
1 file changed, 1 insertion(+)
# 第三次修改提交
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
hyb@139-159-150-152:~/gitcode$ git add ReadMe
hyb@139-159-150-152:~/gitcode$ git commit -m"add version3"
[master d95c13f] add version3
1 file changed, 1 insertion(+)
# 查看历史提交记录,前面的字符串就是每一步操作生成的版本的commit id
# 我们也发现日志后边的记录就是我们commit时给的备注
hyb@139-159-150-152:~/gitcode$ git log --pretty=oneline
d95c13ffc878a55a25a3d04e22abfc7d2e3e1383 (HEAD -> master) add version3
14c12c32464d6ead7159f5c24e786ce450c899dd add version2
cff9d1e019333318156f8c7d356a78c9e49a6e7b add version1
...
现在,如果我们在提交完 version3 后, 发现 version 3 编写错误,想回退到 version2,重新基于
version 2 开始编写。由于我们在这里希望的是将工作区的内容也回退到 version 2 版本,所以需
要用到 --hard 参数,示例如下:
cpp
hyb@139-159-150-152:~/gitcode$ git log --pretty=oneline
d95c13ffc878a55a25a3d04e22abfc7d2e3e1383 (HEAD -> master) add version3
14c12c32464d6ead7159f5c24e786ce450c899dd add version2
cff9d1e019333318156f8c7d356a78c9e49a6e7b add version1
...
#后面指定回退版本的commit id
hyb@139-159-150-152:~/gitcode$ git reset --hard 14c12c32464d6ead7159f5c24e786ce450c899dd
HEAD is now at 14c12c3 add version2
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
我们惊奇的发现,此时ReadMe 文件的内容,已经回退到 version2 了!,当前,我们再次用 git
log 查看一下提交日志,发现HEAD 指向了version2,如下所示:
cpp
hyb@139-159-150-152:~/gitcode$ git log --pretty=oneline
14c12c32464d6ead7159f5c24e786ce450c899dd (HEAD -> master) add version2
cff9d1e019333318156f8c7d356a78c9e49a6e7b add version1
...
到这里一般回退功能就演示完了,但现在如果我后悔了,想再回到 version 3 怎么办?我们可以继续使用 git reset 命令,回退到 version 3 版本,但我们必须要拿到 version 3 的 commitid 去指定回退的版本。
但我们看到了 git log 并不能打印出 version 3 的commit id ,运气好的话我们可以从终端
上去找找之前的记录,运气不好的话 commit id 已经被我们搞丢了=。
因此Git 还提供了一个git reflog 命令能补救一下,该命令用来记录本地的每一次命令,比git log 记录更多。
cpp
hyb@139-159-150-152:~/gitcode$ git reflog
14c12c3 (HEAD -> master) HEAD@{0}: reset: moving to
14c12c32464d6ead7159f5c24e786ce450c899dd
d95c13f HEAD@{1}: commit: add version3
14c12c3 (HEAD -> master) HEAD@{2}: commit: add version2
cff9d1e HEAD@{3}: commit: add version1
94da695 HEAD@{4}: commit: add modify ReadMe file
23807c5 HEAD@{5}: commit: add 3 files
c614289 HEAD@{6}: commit (initial): commit my first file
这样,你就可以很方便的找到你的所有操作记录了,但d95c13f 这个是啥东西?这个是 version
3 的commit id 的部分。没错,Git 版本回退的时候,也可以使用部分commit id 来代表目标版
本。示例如下:
cpp
# 回退到v3
hyb@139-159-150-152:~/gitcode$ git reset --hard d95c13f
HEAD is now at d95c13f add version3
# 查看工作区
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
# 查看log
hyb@139-159-150-152:~/gitcode$ git log --pretty=oneline
d95c13ffc878a55a25a3d04e22abfc7d2e3e1383 (HEAD -> master) add version3
14c12c32464d6ead7159f5c24e786ce450c899dd add version2
cff9d1e019333318156f8c7d356a78c9e49a6e7b add version1
94da6950d27e623c0368b22f1ffc4bff761b5b00 add modify ReadMe file
23807c536969cd886c4fb624b997ca575756eed6 add 3 files
c61428926f3853d4ec6dde904415b0e6c1dabcc6 commit my first file
可往往是理想很丰满,现实很骨感。在实际开发中,由于长时间的开发了,导致 commit id 早就找
不到了,可突然某一天,我又想回退到 version3,那该如何操作呢?貌似现在不可能了。。。
值得说的是,Git 的版本回退速度非常快,因为 Git 在内部有个指向当前分支(此处是master)的
HEAD 指针, refs/heads/master 文件里保存当前 master 分支的最新commit id 。当我们
在回退版本的时候,Git 仅仅是给 refs/heads/master 中存储一个特定的version,可以简单理解
成如下示意图:

撤销修改
如果我们在我们的工作区写了很长时间代码,越写越写不下去,觉得自己写的实在是垃圾,想恢复到上一个版本。
情况一:对于工作区的代码,还没有add
你当然可以直接删掉你目前在工作区新增的代码,像这样:
cpp
# 向ReadMe中新增一行代码
hyb@139-159-150-152:~/gitcode$ git status
On branch master
nothing to commit, working tree clean
hyb@139-159-150-152:~/gitcode$ vim ReadMe
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
This piece of code is like shit #新增代码
hyb@139-159-150-152:~/gitcode$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: ReadMe
no changes added to commit (use "git add" and/or "git commit -a")
# 直接删除代码
hyb@139-159-150-152:~/gitcode$ vim ReadMe
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
hyb@139-159-150-152:~/gitcode$ git status
On branch master
nothing to commit, working tree clean
辛亏我们工作效率不高,才写了一行代码就发现不行了,要是你写了3天,一直都没有提交,该怎么删掉呢?你自己都忘了自己新增过哪些,有人说,我可以git diff xxx 一下,看看差别在删啊,
那你肯定又要花3天时间删代码了,并且很大的概率还会改出bug。一周过去了,你怎么向你的老板交代呢?
Git 其实还为我们提供了更好的方式,我们可以使用git checkout -- [file] 命令让工作区的
文件回到最近一次 add 或 commit 时的状态。 要注意git checkout -- [file] 命令中的
-- 很重要,切记不要省略,一旦省略,该命令就变为其他意思了,后面我们再说。示例如下:
cpp
# 向ReadMe中新增一行代码
hyb@139-159-150-152:~/gitcode$ vim ReadMe
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
This piece of code is like shit #新增代码
# 恢复到上一次 add 或 commit
hyb@139-159-150-152:~/gitcode$ git checkout -- ReadMe
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
情况二:已经add ,但没有commit
add 后还是保存到了暂存区呢?怎么撤销呢?
cpp
# 向ReadMe中新增一行代码
hyb@139-159-150-152:~/gitcode$ vim ReadMe
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
This piece of code is like shit #新增代码
# add 存入暂存区
hyb@139-159-150-152:~/gitcode$ git add ReadMe
hyb@139-159-150-152:~/gitcode$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: ReadMe
让我们来回忆一下学过的git reset 回退命令,该命令如果使用 --mixed 参数,可以将暂存区
的内容退回为指定的版本内容,但工作区文件保持不变。那我们就可以回退下暂存区的内容了!!!
示例如下:
cpp
# --mixed 是默认参数,使用时可以省略
hyb@139-159-150-152:~/gitcode$ git reset HEAD ReadMe
Unstaged changes after reset:
M ReadMe
用 git status 查看一下,发现现在暂存区是干净的,工作区有修改。
cpp
hyb@139-159-150-152:~/gitcode$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: ReadMe
no changes added to commit (use "git add" and/or "git commit -a")
为了丢弃工作区的修改,我们使用上面的git checkout -- [filename]
cpp
hyb@139-159-150-152:~/gitcode$ git checkout -- ReadMe
hyb@139-159-150-152:~/gitcode$ git status
On branch master
nothing to commit, working tree clean
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
恢复了!
情况三:已经add ,并且也commit 了
不要担心,我们可以git reset --hard HEAD^ 回退到上一个版本!不过,这是有条件的,就是
你还没有把自己的本地仓库的版本通过git push推送到远程仓库(我们后面会讲到远程仓库,在实际开发中,我们的项目最终都是要上传到远端进行管理,我们之前所做的git回退一切努力就是为了不污染远程仓库),一但push到远程仓库,这种情况就无法回退了
cpp
# 向ReadMe中新增一行代码
hyb@139-159-150-152:~/gitcode$ vim ReadMe
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
This piece of code is like shit #新增代码
# 提交
hyb@139-159-150-152:~/gitcode$ git add ReadMe
hyb@139-159-150-152:~/gitcode$ git commit -m"test quash"
[master 5f71ae1] test quash
1 file changed, 1 insertion(+)
# 回退
hyb@139-159-150-152:~/gitcode$ git reset --hard HEAD^
HEAD is now at 144a3f8 add file
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
总结

删除文件
在 Git 中,删除也是一个修改操作,我们实战一下, 如果要删除file5 文件,怎么搞呢?如果你这样
做了:
cpp
hyb@139-159-150-152:~/gitcode$ ls
file1 file2 file3 file4 file5 ReadMe
hyb@139-159-150-152:~/gitcode$ rm file5
但这样直接删除是没有用的,因为我们仅仅是在工作区做了修改, git status 命令会立刻告诉你哪些文件被删除了:
cpp
hyb@139-159-150-152:~/gitcode$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: file5
no changes added to commit (use "git add" and/or "git commit -a")
此时,工作区和版本库就不一致了,要删文件,目前除了要删工作区的文件,还要清除版本库的文
件。
一般走到这里,有两种可能:
• 确实要从版本库中删除该文件
• 不小心删错了
对第二种情况,很明显误删,需要使用 git 来进行恢复,很简单,删除也是修改,我们使用上面的回退操作就可以了:
cpp
hyb@139-159-150-152:~/gitcode$ git checkout -- file5
hyb@139-159-150-152:~/gitcode$ ls
file1 file2 file3 file4 file5 ReadMe
对于第一种情况,很明显是没有删完,我们只删除了工作区的文件。第一种方式是先删除git中的文件,然后git add、git commit 将修改的版本推送到版本库
第二种方式是使用 git 提供的 git rm 将文件从暂存区和工作区中删除,然后再git commit :
cpp
hyb@139-159-150-152:~/gitcode$ git rm file5
rm 'file5'
hyb@139-159-150-152:~/gitcode$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
deleted: file5
hyb@139-159-150-152:~/gitcode$ git commit -m"deleted file5"
[master 5476bde] deleted file5
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 file5
hyb@139-159-150-152:~/gitcode$ git status
On branch master
nothing to commit, working tree clean
现在,文件就从版本库中被删除了。