Git学习

1/14/2021 Git

虽然之前有粗浅地学过Git的知识,但过一下子就忘得差不多了,因此想要写一下学习心得巩固、复习一下。

本篇博客是基于廖雪峰老师的Git教程的本人学习心得及笔记,博客内容仅代表个人观点,若有不清晰的地方建议阅读廖老师的原教程:https://www.liaoxuefeng.com/wiki/896043488029600

# 第一步:最基本的指令

  1. git init
    • 说明:这个指令用来在当前目录中创建一个git仓库,用于后续的工作。

    • 实例:

          $ git init
          Initialized empty Git repository in .../...(当前目录)
      

  1. git add (文件名)

    • 说明:这个指令用于向git仓库中添加文件;add指令与commit不同,可以通过多次输入add指令为仓库添加多个文件,最后统一进行commit。
    • 实例: $ git add readme

    • 使用了git add命令给仓库添加了readme文件后,用户可以选择继续add文件,或者使用commit指令向git仓库提交本次操作的所涉及的所有文件。
  2. git commit -m ("一句标注信息")

    • 说明:这个指令是用来告诉git,要提交添加到仓库的所有内容。同时,为这一系列的修改(比如添加文件)标记一段标注文字,方便未来检查该版本的仓库内容所做够的修改。
    • 实例: $ git commit -m "Add a new sentence: Hello World!"

# 第二步:尝试修改文件以解锁更多的Git操作

假设在一次commit之后,我们对文件进行改动并保存(例如我对readme这个文件添加了一句“Git is a good software”在最后一行)。此时……


  1. 用git status查看仓库的状态

    • 因为我们在本地电脑修改了readme这个文件(在最后面添加了一个新句子:Git is a free software),因此当前的readme文件与Git仓库中所提交的不同,因此当我们检查仓库状态时,系统会显示一些提示。

    • 实例: 当输入了 $ git status 这一指令后,系统会进行一下提示:

      $ 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 **这里的modified会用红色标注起来,这是因为本地更新过的readme没有通过git add指令添加到仓库中**
      
      no changes added to commit (use "git add" and/or "git commit -a")
      
    • 这个提示即是告诉用户,相比于之前提交到仓库的源文件,readme文件被修改了;而用户可以通过 git diff 查看两者之前的差异。

  2. 用git diff查看本地文件与git仓库的文件的差异

    • 由于 git status 指令告诉了我们,有readme这个文件被修改了,因此我们可以通过 $ git diff 检查修改了哪些地方。

    • 实例: 当我们输入了 $ git diff 这一指令后,系统会列出发生什么样的修改:

      $ git diff
      diff --git a/readme b/readme
      index 010b6a6..0192d51 100644
      --- a/readme
      +++ b/readme
      @@ -1,2 +1,3 @@
      Hello git!
      Git is a good software!
      +Git is a free software.
      \ No newline at end of file
      
    • 这时候,我们能够看到系统所提示的、所做的修改,正是我们确认的修改,因此我们可以放心的将这个更新后的本地文件readme添加并提交到Git仓库了。

  3. 看一下此时git add的结果

    • 我们将本地更新过的readme文件添加到Git仓库中 $ git add readme

    • 此时系统没有提示,用 $ git status 看一下仓库情况:

      $ git status
      On branch master
      Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
          modified:   readme **这里的modified会被标记为绿色,因为用户已经把更新过的本地readme文件添加到仓库中,等待提交**
      
    • 值得一提的是,这种情况下,因为更新过的本地文件已经被添加到仓库中了,因此让系统执行 $ git diff 已经不会提示哪里被修改了(因为仓库里的readme与本地的readme一模一样

  4. 别忘了最后一步:提交修改

    • 跟之前提到的一样,即使我们用 git add 为仓库增加了文件,我们也是要用 git commit 提交并备注方便我们之后查看该版本的readme文件做了什么样的修改

    • 在使用 git commit 提交并备注过后,我们可以尝试使用 git status 查看仓库的状态

      $ git status
      On branch master
      nothing to commit, working tree clean
      
    • 此时,系统告诉我们,当前目录下的最新本地文件都在仓库里“有一份”了,没什么需要提交了。(这里提到的branch master涉及到了Git的“分支”概念,先埋个伏笔

# 第三步:试水版本回退功能

Git有一个非常大的优势是,它能够记录Git仓库的所有版本;用户能够检查Git仓库的所有版本中所保存的文件信息,如果后续用户对本地文件做了不满意的修改,而又同时不小心保存了,那么可以通过Git仓库,选择合适的版本将保存在Git仓库的那个版本的文件覆盖当前本地的不满意的文件。


  1. 使用 git log 查看当前目录中Git仓库的版本信息

    • git log 这个指令其实可以理解为是查看历史记录,它不仅能够看到版本信息,还能看到该版本所提交 git commit 的时间。

    • 实例:

      $ git log
      commit 7737cfc9bda596a9edb3c68e36fefa7216abd467 (HEAD -> master)
      
          test
      
      commit e840f0f0aea61d85918bef367fa526260076c80f
      
          add a new file and append a new sentence of readme
      
      commit a1e39330e3412bd74d697e7903f7dac67065c0dd
      
          add a new sentence: Git is a free software
      
      commit bd4c964a31615400b73d0bb795828a9dea268652
      
          add a new line: Git is a good software!
      
      commit 87a8b398316080cf5e719874c9777ad67a4e7d95
      
    • 可以看到,系统直接将我们所提交过的所有版本都显示出来了,其中,需要注意的是: 最上面的那个版本信息是当前版本信息,因此我们能看到它有一个 HEAD 作为标记。

    • 这个 HEAD 有很大的用处,因为在Git中,我们可以用 HEAD^ 来表示当前版本的上一个版本,用 HEAD^^ 来表示上上个版本,但如果版本过多,就不适合使用 ^ 这个符号了,这种情况下可以使用 HEAD~(数字) 这种表示形式,其中 “(数字)” 改为上n个版本的n,比如我要回到上10个版本,可以用 HEAD~10

    • 现在我们使用 git reset 指令回归到上一个版本

      $ cat readme
      Hello git!
      Git is a very good software!
      Git is a free software.
      test
      
      $ git reset --hard HEAD^
      HEAD is now at e840f0f add a new file and append a new sentence of readme
      
      $ cat readme
      Hello git!
      Git is a very good software!
      Git is a free software.
      
    • 如上所示,为了进行一个文件内容对比,我先使用了 cat 指令在CMD中显示在没有进行版本回退时(即HEAD)的readme文件的内容,其中它在最后面有一行"test";当我进行了版本回退后,再次用 cat 指令查看readme文件时,就没有最后一行了(因为上一个版本没有添加这一行内容)

    • 需要注意的是,由于我们进行了回溯上个版本的操作,因此此时用 git log 是看不到原来那个没有回溯的版本的,取而代之的是原本是 HEAD^ 的版本变为 HEAD

  2. 小技巧:可以用 git diff 看版本之前的差异

    • 比如我想看前一个版本和当前版本的文件内容差别,可以使用刚才提到的 HEAD^HEAD

    • $ git diff HEAD^ HEAD
      diff --git a/readme b/readme
      index 4dff437..5cc9d26 100644
      --- a/readme
      +++ b/readme
      @@ -1,3 +1,4 @@
      Hello git!
      Git is a very good software!
      -Git is a free software.
      \ No newline at end of file
      +Git is a free software.
      +test
      \ No newline at end of file
      
    • 系统显示的是当前版本与上一个版本的区别是在readme文件中新增了一行 "test" 内容,这也与事实相符。


  1. 如果我不小心回退错了!又想回到原本的最新版本,怎么办?
    • 此前我们使用 git reset 进行版本回退时,配合使用的是 HEAD^ 指针,实际上回退也可以使用版本号进行,即我们在输入 git log 指令后,系统输出的那一串数字与字母组合的字符串

    • 因为版本回退了之后,我们使用 git log 就看不到原来版本的版本号了;但这并不意味着我们不能回到原来的最新版本,实际上我们还有一个途径找回它的版本号: git reflog 指令

    • $ git reflog
      e840f0f (HEAD -> master) HEAD@{2}: reset: moving to HEAD^
      7737cfc HEAD@{3}: commit: test
      e840f0f (HEAD -> master) HEAD@{4}: commit: add a new file and append a new sentence of readme
      a1e3933 HEAD@{5}: commit: add a new sentence: Git is a free software
      bd4c964 HEAD@{6}: commit: add a new line: Git is a good software!
      87a8b39 HEAD@{7}: commit (initial): wrote a readme file
      
    • 通过上述指令,我们可以找回原来最新版本的版本号为 7737cfc,因此如果想回到那个版本的话,可以使用指令 git reset --hard 7737cfc

    • 回到 test 版本的情况如下

      $ git reset --hard 7737
      HEAD is now at 7737cfc test
      

# 第四步:了解工作区和暂存区(stage)

  1. 工作区、暂存区和master分支的概念

    • 工作区实际上就是我们在电脑上能够看到的,正在使用的当前目录,而在当前目录中,有一个 .git 文件夹实际上是Git的版本库;在这个版本库中,有两个很重要的部分:暂存区(stage或者也叫做index)和 Git 自动给我们创建的master分支,同时我们之前提到的 HEAD 实际上是指向master分支的一个指针。
    • 当我们使用 git add 把文件添加到 Git仓库时,其实是把文件添加修改添加到暂存区;当我们使用 git commit 时,就是把暂存区的所有内容提交到当前分支中(由于Git默认给用户创建了一个master分支,所以暂存区的内容会被提交到master分支中)
  2. 用实际例子演示一下添加到暂存区的工作

    • 我们随便修改一下当前目录的readme文件,同时新建一个LICENSE文件,此时Git仓库的状态如下
      $ 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
      
      Untracked files:
      (use "git add <file>..." to include in what will be committed)
              LICENSE
      
      no changes added to commit (use "git add" and/or "git commit -a")
      

    • 由于我们没有把修改过的readme文件和新建的LICENSE文件修改添加到Git版本库的暂存区中,系统会询问是否使用 git add 指令; 当我们依次将新的readme文件和LICENSE文件添加到Git仓库暂存区后查看仓库信息
      $ git status
      On branch master
      Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
              new file:   LICENSE
              modified:   readme
      

    • 可以用下图来表示在这个过程
      add
    git add 指令背后的工作情况

  1. 用实际例子演示一下提交到分支的工作
    • 由于刚才我们已经使用了 git add 将文件修改添加到了Git仓库的暂存区,如果确认无误后我们可以将其提交到分支
    • $ git commit -m "test stage"
      [master 9ed8c7c] test stage
      2 files changed, 2 insertions(+), 1 deletion(-)
      create mode 100644 LICENSE
      
      $ git status
      On branch master
      nothing to commit, working tree clean
      
    • 这样我们就完成了将暂存区的内容一次性全部提交到分支,同时因为分支的内容与本地目录(即工作区)完全相同,因此当我们执行 git status 指令后,系统会提示分支是干净的,可以用下图来表示这个过程
      commit
      git commit 指令背后的工作情况

# 第五步:理解Git工作原理——管理修改

  1. 小实验:通过add与commit指令理解Git的工作

    • 在上面我们已经提过了,Git仓库实际上由暂存区和分支构成,当使用 git add 指令时,系统会将指定文件的修改情况添加到暂存区;当使用 git commit 指令时,系统会将暂存区的内容传到默认的 master 分支中

    • 对于Git来说,对于当前目录的修改可以是对于当前目录的文件的内容的 “增删改”,也可以是新建一个文件或删除一个文件;当我们使用 git add 指令时,Git实际上是将当前目录的修改情况增加到暂存区中,并不是将文件拷贝到暂存区中

    • 我们来做一个实验来帮助理解,这是当前readme文件的内容:

      $ cat readme
      Hello git!
      Git is a very good software!
      Git is a free software.
      Git has a mutable index call stage
      Git tracks changes
      
    • 我们将其添加到暂存区,并查看状态信息:

      $ git add readme
      
      $ git status
      On branch master
      Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
              modified:   readme
      
      
    • 现在Git等待我们将暂存区的内容添加到 master 分支中,不过我们再修改一下 readme文件(注意,此时我们在 readme文件中的最后一行添加了 "of files" 这两个单词):

      $ cat readme
      Hello git!
      Git is a very good software!
      Git is a free software.
      Git has a mutable index call stage
      Git tracks changes of files
      
    • 再提交暂存区的内容并查看状态信息:

      $ git commit -m "add a new line to readme"
      [master 80694d0] add a new line to readme
      1 file changed, 2 insertions(+), 1 deletion(-)
      
      $ 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 commit 之后检查状态信息是显示 "nothing to commit, working tree clean",但在这里则显示的是文件内容有差异,那我们继续用 git diff 看一下:

      $ git diff
      diff --git a/readme b/readme
      index 6cfcf24..c15035d 100644
      --- a/readme
      +++ b/readme
      @@ -2,4 +2,4 @@ Hello git!
      Git is a very good software!
      Git is a free software.
      Git has a mutable index call stage
      -Git tracks changes
      \ No newline at end of file
      +Git tracks changes of files
      \ No newline at end of file
      
    • 系统显示 readme文件最后一行添加了两个词,因此说明版本库中的 readme文件是执行 git add readme 的版本

  2. 结论:没有通过 git add 添加到暂存区的修改,就不能加入到 git commit 中

    • 如果Git管理的是文件,它此时保存的应该是含 "of files" 这两个单词的最新版本,而不是上一个版本;因此证明,Git是通过跟踪和管理修改进行工作的

# 第六步:Git的撤销修改

  1. 两个有用的新指令

    • 如果我们在本地修改了某个文件,但我们没有将该文件(修改后的新版本)添加到暂存区,我们可以使用 git checkout -- (文件名) 撤销对其的本地修改,这个指令的工作原理是让工作区的这个文件回到最近一次 git addgit commit 时的状态

      • 如果此时暂存区没有内容,那么这个指令是让工作区的这个文件回到 git commit 时的状态;如果此时暂存区有内容,那么这个指令是让工作区的这个文件回到 git add 时的状态
    • 如果我们在本地修改了某个文件,且我们不小心将这个新版本通过 git add 添加到暂存区了,我们可以通过 git reset HEAD (文件名) 将Git仓库暂存区的关于该文件的内容还原回执行了 git add 前的状态;还原了之后,我们就可以用上面提到的 "checkout" 指令撤销对其的本地修改了

  2. 总结

    1. 如果用户对于 readme文件 的本地修改不满意,但没有将这个修改后的版本使用 git add 添加到暂存区,那么可以使用 git checkout -- readme 来撤销对于 readme文件 的本地修改

    2. 如果用户对于 readme文件 的本地修改不满意,但又不小心将这个修改后的版本使用了 git add 添加到了暂存区,那么要做两部:

      • 首先要通过 git reset HEAD readme 将这个修改后的版本从暂存区回退到没修改的版本
      • 再通过 git checkout -- readme 撤销本地修改(其实这个指令实际上是将本地同名文件回退为最新一次 git addgit commit 的版本
    3. 如果用户对于readme文件 的本地修改不满意,但既不小心添加到了暂存区,又不小心传到了分支中,那么想回到没有进行修改的版本就要使用之前提到的 git reset --hard HEAD^ 指令将Git仓库回退到上一个版本,同时将本地文件回退到那个版本的样子

# 第七步:使用Git恢复删除的文件

  1. 如果我们不小心在当前目录下删除了某个文件,但我们没有在Git仓库中删掉它,那么我们可以使用上文提到的 git checkout -- (文件名) 还原;因为对于Git来说,删除文件也是一种修改,我们可以通过撤销修改来实现文件的恢复
  2. 如果我们确定要在当前目录下删除某个文件,同时又不想它保存在Git仓库中,那么我们可以使用 git rm (文件名) 来删除它在Git仓库的记录

# 第八步:Git的分支管理

使用Git的分支思想是:当你进行多人开发等多人协作时,由于不同成员的工作进度不同,因此会影响团队的工作效率;使用Git的分支功能,每个人能够先在自己的分支里进行工作,当需要上传源代码与其他成员交流时可以上传已经完成的部分到公用分支中;当团队内每位成员的工作完成后可以进行分支合并汇总工作情况;因此Git鼓励开发者广泛使用分支功能

  1. 创建、切换、删除及合并分支

    • 之前提到的 HEAD 指针实际上是指向当前分支的,而Git为我们默认创建的 master 分支才是指向每一次使用 git commit 上传的提交,可以理解为每次提交都算作一个节点(类似 git log 所反馈的每一个单元,为方便起见,我们称之为 "commit节点"),随着不断使用 git commitmaster 分支也会越来越长

    • 当我们创建一个新的分支时,它是基于最新的commit节点的,比如我们创建一个叫作 "dev" 的新分支并进入到该分支后,我们对于工作区文件的修改即是处于该分支的

    • 在实验之前我们学一下新指令:

      • git switch -c (分支名) 这个指令用于创建并进入该分支(实际上按照指令的顺序,应该是“先进入再创建分支”,因为 "-c" 参数的含义即是 "create")
      • git switch (分支名) 这个指令就是简单的切换到指定分支
      • git branch 这个指令用于查看Git版本库所有的分支情况
      • git branch -d (分支名) 这个分支增加了一个 "-d" 参数,其含义为删除指定分支
      • git merge (分支名) 这个指令的含义为合并指定分支于当前分支中,比如我们在 master 分支执行 git merge dev 时,其含义即为将 dev 分支合并到 master 中
    • 我们来做一下小实验,首先这么做

      $ cat readme
      Hello git!
      Git is a very good software!
      Git is a free software.
      Git has a mutable index call stage
      Git tracks changes of files
      I have created a new branch named "dev"
      
      $ git switch -c dev
      Switched to a new branch 'dev'
      
      ** 此时我们已经进入了 dev 分支,在文本编辑器中修改 readme 文件,在其最后一行添加一行字符串 "Test the merging" **
      ** 以下指令是在dev分支模式下执行的 **
      
      $ git add readme
      
      $ git commit -m "This version is committed by dev branch"
      [dev 877f677] This version is committed by dev branch
      1 file changed, 2 insertions(+), 1 deletion(-)
      
    • 之后,我们回到熟悉的 master 分支看看会发生什么

      $ git switch master
      Switched to branch 'master'
      
    • 此时我们会发现,readme 的内容会变为没有添加 "Test the merging" 的版本,因为这是我们保存在 master 分支的最新版本

    • 那我们尝试一下合并分支

      $ git merge dev
      Updating 36abf95..877f677
      Fast-forward
      readme | 3 ++-
      1 file changed, 2 insertions(+), 1 deletion(-)
      
      $ git branch -d dev
      Deleted branch dev (was 877f677).
      
    • 合并之后,dev 分支的最新版本的 readme 文件所更新的内容就出现了,同时打开文本编辑器也能发现确实有了 "Test the merging" 这一个句子;由于合并之后 dev 分支可以删除了,因此我们使用 git branch -d 指令删除该分支

  2. 分支冲突问题 分支合并所蕴含的问题是,当两个分支的同名文件的内容在相同位置有出入,那么应该怎么抉择呢?

    • 我们先创建一个 "feature1" 的分支,并修改 readme 文件的最后一行为 "Creating a new branch is quick AND simple"

      $ git switch -c feature1
      Switched to a new branch 'feature1'
      
      $ cat readme
      Hello git!
      Git is a very good software!
      Git is a free software.
      Git has a mutable index call stage
      Git tracks changes of files
      I have created a new branch named "dev"
      Creating a new branch is quick AND simple
      
      $ git add readme
      
      $ git commit -m "AND simple"
      [feature1 eaf2379] AND simple
      1 file changed, 1 insertion(+), 1 deletion(-)
      
    • 之后回到 master 分支并修改 readme 文件的最后一行为 "Creating a new branch is quick & simple"

      $ git switch master
      Switched to branch 'master'
      
      ** 和之前一样,回到 master 分支后是显示该分支的最新版本的 readme文件内容 **
      ** 此时修改 readme 文件 **
      
      $ cat readme
      Hello git!
      Git is a very good software!
      Git is a free software.
      Git has a mutable index call stage
      Git tracks changes of files
      I have created a new branch named "dev"
      Creating a new branch is quick & simple
      
      $ git add readme
      
      $ git commit -m "& simple"
      [master ac432be] & simple
      1 file changed, 1 insertion(+), 1 deletion(-)
      
      $ git merge feature1
      Auto-merging readme
      CONFLICT (content): Merge conflict in readme
      Automatic merge failed; fix conflicts and then commit the result.
      
    • 这时候我们想要执行将两个分支合并的指令时,会发现系统提示两条分支在 readme 文件出现冲突,无法使用快速合并,需要修复冲突。由于快速合并失败,Git 正在等待我们修改冲突,这时候 Git 处于临时的 "MERGING" 状态,当用户修复好冲突并进行提交后,才会回到原本的 master 分支状态, 查看 readme 文件的内容,可以发现 Git 帮我们做了一下标记

      $ cat readme
      Hello git!