# Git时光穿梭机

此为学习笔记,教学文档请移步廖雪峰的官方网站 (opens new window),本篇文章主要记录版本会退和恢复文件命令。

在git基础里面我们已经成功添加并提交了readme.txt文件,现在我们对文件进行修改:

现在使用git status查看结果

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   readme.txt

no changes added to commit (use "git add" and/or "git commit -a")
1
2
3
4
5
6
7
8
9

git status命令用来查看仓库当前的状态,上面命令的输出告诉我们,readme.txt文件被修改过了,但还没有准备提交的修改。

git status只是告诉我们有修改,我们可以通过git diff来查看具体的修改内容:

git diff  (file name)显示工作区该文件与版本库中当前分支下该文件的差异。

$ git diff
diff --git a/readme.txt b/readme.txt
index c3295f1..a440e5c 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-git is a version controller system.
+git is a distributed controller system.
 git is free software
1
2
3
4
5
6
7
8
9

详解:

  1. diff --git a/readme.txt b/readme.txt——对比两个文件,其中a改动前,b是改动后,以git的diff格式显示;
  2. index c3295f1..a440e5c 100644——两个版本的git哈希值,index区域(add之后)的c3295f1对象和工作区域的a440e5c对象,100表示普通文件,644表示权限控制;
  3. --- a/readme.txt +++ b/readme.txt——其中减号表示变动前,加好表示变动后。
  4. @@ -1,2 +1,2 @@——@@表示文件变动描述合并显示的开始和结束,其中-+表示变动前后,逗号前是起始行位置,逗号后为从起始行往后几行。合起来就是变动前后都是从第1行开始,变动前文件往后数2行对应变动后文件往后数2行。
  5. -git is a version controller system. +git is a distributed controller system. ——+表示增加了这一行,-表示删除了这一行,没符号表示此行没有变动。

知道对齐做了什么修改之后,我们就可以提交修改了,步骤与提交新文件一样,都是两个步骤,第一步git add

$ git add readme.txt
1

添加成功后,我们可以使用git status查看当前仓库的状态

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   readme.txt
1
2
3
4
5
6

上面的信息告诉我们将要被提交的修改为readme.txt,然后我们可以使用git commit进行提交。

$ git commit -m 'version replaced by distributed in line 1'
[master 2e0bdc4] version replaced by distributed in line 1
 1 file changed, 1 insertion(+), 1 deletion(-)
1
2
3

提交后,再次使用git status查看状态,

$ git status
On branch master
nothing to commit, working tree clean
1
2
3

git告诉我们,没有文件需要被提交,且工作目录是干净的。

# 版本回退

我们先再次修改一下readme.txt,并进行提交,修改内容如下:

Git is a distributed version control system.
Git is free software distributed under the GPL.
1
2

提交如下:

$ git add readme.txt
$ git commit -m 'appened GPL'
[master af61833] appened GPL
 1 file changed, 2 insertions(+), 2 deletions(-)
1
2
3
4

# 查看提交历史

在git中我们可以通过git log命令来查看提交的历史记录

commit af6183376d218e36978735de48e9db0790fcb743 (HEAD -> master)
Author: lf <781723804@qq.com>
Date:   Sun Jul 7 15:03:24 2019 +0800

    appened GPL

commit 2e0bdc40e2c63128b955fd0e6c3c3cadc895c684
Author: lf <781723804@qq.com>
Date:   Sun Jul 7 14:45:14 2019 +0800

    version replaced by distributed in line 1

commit 1210adbed7497662a6a0c88bd53adb69e497df69
Author: lf <781723804@qq.com>
Date:   Sun Jul 7 01:44:27 2019 +0800

    write a readme file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

git log显示从近到远的提交日志,我们可以看到总共有3次提交,最近的一次是appened GPL,上一次是version replaced by distributed in line 1,最早的一次是write a readme file

也可以使用git log --pretty=oneline而使一条记录显示一行

这里黄色的部分是commit id(版本号)是一个SHA1计算出来的一个非常大的数字,用十六进制表示。

每提交一个新版本,git会将它们自动串成一条时间线。

# 回退到历史版本

如果我们想要把readme.txt回退到上一版本,也就是 version replaced by distributed in line 1那个版本,需要以下的步骤:

首先,必须知道当前版本,在Git中HEAD表示当前版本,也就是appened GPL版本,想要到上一个版本,就是HEAD ^,上上个版本是HEAD ^^,如果是前10个版本,则可以用HEAD~10,

我们需要使用git reset进行版本回退

$ git reset --hard HEAD^
HEAD is now at 2e0bdc4 version replaced by distributed in line 1
1
2

--hard参数有啥意义?这个后面再讲,现在你先放心使用。

我们可以看看 readme.txt的内容是不是version replaced by distributed in line 1版本

$ cat readme.txt
git is a distributed controller system.
git is free software
1
2
3

我们可以看到已经还原到了上一个版本,这时我们使用git log查看版本库的状态

最新版本的append GPL已经不见了,如果想要回到这个版本,则需要以下方法

# 返回未来的版本

想要恢复到append GPL的版本,则需要首先找到其commit id(版本号),这时就需要git reflog来查看git记录每一次的命令。

这时我们可以看到,appened GPL的版本号为af61833,然后我们使用git reset --hard  (commit id)就可回到未来的某个版本号。

$ git reset --hard af61833
HEAD is now at af61833 appened GPL
1
2

这是我们使用cat命令查看文件内容:

$ cat readme.txt
git is a distributed version controller system.
git is free software distributed under GPL
1
2
3

说明已经将版本转换回来,Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD从指向version replaced by distributed in line 1

┌────┐
│HEAD│
└────┘
   │
   │    ○ append GPL
   │    │
   └──> ○ version replaced by distributed in line 1
        │
        ○ wrote a readme file
1
2
3
4
5
6
7
8
9

改为指向append GPL

┌────┐
│HEAD│
└────┘
   │
   └──> ○ append GPL
        │
        ○ version replaced by distributed in line 1
        │
        ○ wrote a readme file
1
2
3
4
5
6
7
8
9

# 工作区和暂存区

Git与SVN等其他版本库的一个不同之处就是存在一个暂存区的概念。

# 工作区(Working Directory)

即电脑里能够看到的目录,如之前建立的learngit文件夹就是一个工作区。

# 版本库(Repository)

工作区里面的隐藏目录.git就是Git版本库。

Git版本库里面最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及的指针HEAD指向master分支

我们将文件提交到Git版本库时,是分两步进行的:

  1. git add进行添加文件,实际上是将文件添加早暂存区;
  2. git commit进行文件提交,实际是将 暂存区中的所有文件提交到当前分支。

因为我们使用的是Git为我们自动创建的master分支,所以提交到的也是master分支

我们先对readme.txt文件进行修改,在最后一行添加如下内容:

git is a distributed version controller system.
git is free software distributed under GPL.
git has a mutable index called stage.
1
2
3

然后在工作区新建一个license文本文件,里面内容随便填写。 先用git status查看版本库的状态

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   readme.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        license.txt

no changes added to commit (use "git add" and/or "git commit -a")
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Git 告诉我们readme.txt文件被修改了,而license.txt文件还从来没有被提交过,所以其状态为Untracked

现在使用两次命令git add之后,将readme.txtlincese.txt文件都添加到暂存区后,使用git status查看状态

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   license.txt
        modified:   readme.txt
1
2
3
4
5
6
7

这时,暂存区变为如图所示:

所以git add相当于将要提交的修改添加到暂存区(stage),然后通过git commit将暂存区的所有修改提交到分支。

$ git commit -m 'how stage works'
[master 35561ed] how stage works
 2 files changed, 3 insertions(+), 1 deletion(-)
 create mode 100644 license.txt
1
2
3
4

一旦提交之后,如果没有对工作区进行修改,则工作区就是“干净”的,类似于清空了购物车。

$ git status
On branch master
nothing to commit, working tree clean
1
2
3

这时暂存区中则没有任何内容:

# 管理修改

Git跟踪管理的是修改,而不是文件。这是Git比其他版本控制系统优秀的原因。

例如,我们在readme.txt文件中天剑一行内容:

$ cat readme.txt
git is a distributed version controller system.
git is free software distributed under GPL.
git has a mutable index called stage.
git tracks change
1
2
3
4
5

然后添加:

然后再次修改readme.txt文件

$ cat readme.txt
git is a distributed version controller system.
git is free software distributed under GPL.
git has a mutable index called stage.
git tracks changes of files
1
2
3
4
5

提交:

$ git commit -m 'git tracks changes'
[master fada47f] git tracks changes
 1 file changed, 1 insertion(+)
1
2
3

查看状态

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   readme.txt

no changes added to commit (use "git add" and/or "git commit -a")
1
2
3
4
5
6
7
8
9

这表明第二次修改并没有提交,当时的操作过程如下:

第一次修改 -> git add -> 第二次修改 -> git commit
1

由于Git管理的是修改,当第一次git add之后,第一次修改被添加到暂存区,准备提交,第二次修改并没有进行git add将修改添加到暂存区,因此git commit只是把暂存区的文件进行提交,也就是第一次修改文件。

提交后,可以使用git diff HEAD -- readme.txt查看工作区和版本库里面最新版本的区别。

可见第二次修改并没有被提交。

可以在第二次修改完成后统一进行提交,也可以修改一次,进行一次git add

# 撤销修改

当地随便在readme.txt文件下随便写了一行文字,

$ cat readme.txt
git is a distributed version controller system.
git is free software distributed under GPL.
git has a mutable index called stage.
git tracks changes of files
my lalla
1
2
3
4
5
6

在提交之前,你发现这行有错误,你可以删除掉最后一行,然后手动将文件恢复到上一个版本的状态,如果使用git status查看

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   readme.txt

no changes added to commit (use "git add" and/or "git commit -a")
1
2
3
4
5
6
7
8
9

git会提示你git checkout -- file可以丢弃工作区的修改

$ git checkout -- readme.txt
1

命令git checkout -- readme.txt文件的意思就是把,readme.txt文件在工作区的修该全部撤销,其中包括两种情况:

  1. readme.txt文件修改后,还没有添加到暂存区,则撤销修改回与版本库一样的状态
  2. readme.txt文件已经添加到暂存区,之后又进行了修改,则撤销修改回添加到暂存区之后的状态。

总之,就是让这个文件回到最近一次git commitgit add时的状态。

现在查看文件的状态:

$ cat readme.txt
git is a distributed version controller system.
git is free software distributed under GPL.
git has a mutable index called stage.
git tracks change
1
2
3
4
5

可以发现文件内容复原了。

如果添加了错误内容,并git add将其添加到暂存区域了,这是我们在使用git status查看状态

Git告诉我们使用git reset HEAD file可以把暂存区的修改撤销掉(unstage),重新放回到工作区

$ git reset HEAD readme.txt
Unstaged changes after reset:
M       readme.txt
1
2
3

git reset命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD时,表示最新的版本。

再次使用git status查看,可以看到暂存区是干净的,工作区有修改

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   readme.txt

no changes added to commit (use "git add" and/or "git commit -a")
1
2
3
4
5
6
7
8
9

这时我们可以使用git checkout -- file丢弃工作区的修改

如果已经提交到了版本库中,则需要使用版本回退来撤销修改。前提是还没推送到远程库

# 删除文件

在Git中,删除也是一种操作,我们先添加一个test.txt文件并提交:

一般我们会在工作区中删除文件,或者在命令行使用rm命令删除文件

这个时候Git知道删除了文件,所以工作区和版本库中文件不一致,可以使用git status来查看那些文件被删除了

$ git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    test.txt

no changes added to commit (use "git add" and/or "git commit -a")
1
2
3
4
5
6
7
8
9
  1. 如果确实要删除此文件,根据提示可以使用git add/rm file删除,并使用git commit进行提交

这时,文件就从版本库中删除了。

小提示:先手动删除文件,然后使用git rm file和git add  file效果是一样的。都将文件修改信息添加到了暂存区。

2.不小心错误的删除了文件,因为版本库中还存在,所以我们可以很轻松的把文件回复到最新版本。

$ git checkout -- file
1

注意:从来没有被添加到版本库就被删除的文件,是无法恢复的!