前言

这部分将讲述Git相关的基本知识和操作方法。

各种Git命令操作,需要鼠标右键选择,Git Bash here来开始。

基本操作

  • git init——初始化仓库

    要使用Git进行版本管理,必须先初始化仓库。Git是使用git init命令进行初始化的。命令示例:

    1
    git init
    image-20220203172122036

    如果初始化成功,执行了git init命令的目录下就会生成.git目录。这个目录里存储着管理当前目录内容所需的仓库数据

    .git目录默认隐藏,需要开启查看隐藏文件才可以查看到

    在Git中,我们将这个目录的内容称为“附属于该仓库的工作树”。文件的编辑等操作在工作树中进行,然后记录到仓库中,以此管理文件的历史快照。如果想将文件恢复到原先的状态,可以从仓库中调取之前的快照,在工作树中打开。开发者可以通过这种方式来获取以往的文件,具体的操作指令后面会说明。

  • git status——查看仓库状态

    git status命令用于显示Git仓库的状态。

    工作树和仓库在被操作的过程中,状态会不断发生变化。在Git操作的过程中,要常看仓库状态。命令示例:

    1
    2
    3
    4
    5
    6
    $ git status
    On branch master

    No commits yet

    nothing to commit (create/copy files and use "git add" to track)

    结果显示我当前处于master分支下。关于分支我们会在后面讲到。接着还显示了没有可提交的内容。所谓提交(Commit),是指“记录工作树中所有文件的当前状态”。

    没有可提交内容,就是说明我们当前建立这个仓库中还没有记录任何文件的任何状态。这里,创建一共Readme文件作为示例,为第一次提交做前期准备。

    image-20220203184854865

    可以看到在Untracked files中显示了Readme.md文件。类似地只要对Git的工作树或仓库进行操作,git status命令的显示结果就会发生变化。

  • git add——向暂存区中添加文件

    如果只是用Git仓库的工作树创建了文件,那么该文件并不会被计入Git仓库的版本管理对象当中。因此我们使用git status命令查看ReadMe.md文件时,它会显示在Untracked files里。

    要想让文件成为Git仓库的管理对象,就需要用git add命令将其加入暂存区(Stage或者Index)中暂存区是提交之前的一个临时区域

    image-20220205105924838

    将ReadMe.md文件加入暂存区后,git status命令的显示结果发生了变化。可以看到,ReadMe.md文件显示在Changes to be committed中了。

  • git commit——保存仓库的历史记录

    git commit命令可以将当前暂存区中的文件实际保存到仓库的历史记录中。通过这些记录,我们就可以在工作树中复原文件。

    1. 记录一行提交信息

      命令示例:

      1
      git commit -m "这里是提交信息,是对提交的描述"
      image-20220205110529770
    2. 记录详细的提交信息

      上述命令示例,提交了一行简单的提交信息,如果希望更加详细的描述提交信息,就不需要加-m,直接执行git commit命令。如下所示:

      1
      2
      #不需要加-m了
      git commit

      执行后编辑器会启动显示如下效果:

      1
      2
      3
      4
      5
      6
      7
      8

      # Please enter the commit message for your changes. Lines starting
      # with '#' will be ignored, and an empty message aborts the commit.
      #
      # On branch master
      # Changes to be committed:
      # new file: ReadMe.md
      #

      在编辑器中记述提交信息的格式如下(可以不用遵守这个格式,此格式只是提高可读性):

      • 第一行:用一行文字简要描述提交信息
      • 第二行:空行
      • 第三行以后:记录更改的原因和详细的内容

      只要按照上面的格式输入,今后便可以通过确认日志命令或者工具看到这些记录。命令示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      这是一行注释

      这是详细的注释信息,可以一直写,都是文件更改注释描述
      # Please enter the commit message for your changes. Lines starting
      # with '#' will be ignored, and an empty message aborts the commit.
      #
      # On branch master
      # Changes to be committed:
      # new file: ReadMe.md
      #

      在以#标为注释的Changes to be committed(要提交的更改)栏中,可以查看本次提交中包含的文件。将要提交信息按格式记录完毕后,保存然后关闭编辑器,以#标注为注释的行不必删除,随后,刚才记录的提交信息就会被提交。

    3. 中止提交

      如果在编辑器启动后想中止提交,直接将提交信息留空关闭编辑器即可。

    4. 查看提交后的状态

      执行完毕git commit命令后再来查看当前状态:

      image-20220205113345688
  • git log——查看提交日志

    git log命令可以查看以往仓库中的提交日志。包括什么人在什么时候进行了提交和合并,以及操作前后的差别等。现在来查看之前git commit的命令是否被记录了:

    image-20220205113611232

    可以看到已经记录了我们的提交记录。**commit后面显示的“4b68….”,是指向这个提交的哈希值。Git的其他命令中,在指向提交时也会用到这个哈希值。Author是提交人的用户名和邮箱地址。Date栏显示的提交时间,然后后面就是提交的信息。**

    1. 只显示提交信息的第一行

      如果只想让程序显示第一行简述信息,可以在git log命令后加上--pretty=short

      1
      git log --pretty=short
    2. 只显示指定目录,文件的日志

      只要在git log命令后面加上目录名或者文件名就可以显示于该目录或者文件相关的记录。

      1
      2
      # 命令示例:
      git log ReadMe.md
      image-20220205114741582
    3. 显示文件的改动

      如果想查看文件提交所带来的改动,可以加上-p参数。

      1
      git log -p

      如果只想查看某文件的提交日志以及前后差异可以使用如下格式:

      1
      2
      3
      4
      5
      #命令格式:
      git log -p 文件名

      #命令示例:
      git log -p ReadMe.md
  • git diff——查看更改前后的差别

    git diff命令可以查看工作树,暂存区,最新提交之间的差别。

    现在我们在之前提交的ReadMe.md中写点东西:

    1
    这是新写的东西
    1. 查看工作树和暂存区的区别

      执行git diff命令,查看工作树与暂存区的差别。

      image-20220205125519560

      因为我们并没有使用git add命令将工作树的文件添加到暂存区,所以程序只显示工作树与最新提交状态之间的差别。

      +号标出的是新添加的行,被删除的行用-标出,由此可以看出只添加了一行。

      现在使用git add将修改后的文件添加到暂存区

    2. 查看工作树和最新提交的差别

      如果我们现在执行git diff命令,由于工作树和暂存区的状态没有差别,结果是什么也不会显示。要查看与最新提交的差别,需要执行如下命令:

      1
      git diff HEAD
      image-20220205125900504

      然后我们就可以直接进行提交git commit了,提交完毕后使用git log命令检查一下提交是否被记录。

如果进入git log日志最后发现了<End>,说明进入日志文件,这个时候按q就可以退出

分支的操作

在进行多个并行作业时,我们会用到分支。在这类并行开发的过程中,往往同时存在多个最新代码状态。如下图所示,从master分支创建分支A和分支B后,每个分支中都拥有自己的最新代码。master分支是Git默认创建的分支,因此基本上所有开发都是以这个分支为中心进行的。

image-20220205131656707

不同的分支中,可以同时进行完全不同的作业。等该分支的作业完成之后再与master分支合并。比如A分支的作业结束后再与master合并。如下图:

image-20220205131939324
  • git branch——显示分支一览表

    git branch命令可以将分支名列表显示,同时可以确认当前所在的分支。

    image-20220205132950335

    可以看到master分支左侧有*号,表示这是我们当前所在的分支。也就是说,我们正在master分支下进行开发。结果中没有显示其他分支名,表示本地仓库中只存在master一个分支

  • git checkout -b——创建,切换分支

    如果想以当前的master分支为基础创建新的分支,需要用到git checkout -b命令。命令格式如下:

    1
    git checkout -b 需要创建的分支名称
    image-20220205145339982

    实际上git checkout -b命令,是下面的命令合并效果:

    1
    2
    3
    4
    #创建分支test
    git branch test
    #指针切换到分支test,之前一直默认在主分支即master上
    git checkout test

    如果在test分支的左边有*号,则表示目前指针指向该分支,如果这个时候正常开发修改代码,再提交的话,代码就会提交到test分支下。像这样不断对一个分支进行提交的操作,称为“培育分支”。

    如果我们想要快速切换回上一个分支,可以使用-代替分支名,命令示例:

    1
    git checkout -
    image-20220205151438821
  • 特性分支

    Git与SVN等集中型版本管理系统不同,创建分支不需要连接中央仓库,所以能够轻松创建分支。因此,当今大部分工作流程中都用到了特性(Topic)分支。

    特性分支顾名思义,是集中实现单一特性(主题),除此之外不进行任何作业的分支。在日常快开发中,往往会创建数个特性分支,同时在此之外再保留一个随时可以发布软件的稳定分支。稳定分支的角色同时由master分支担当。

    image-20220205154334634

    例如之前创建的分支A,这一分支主要实现A需求,除A需求之外不进行任何作业,即便在开发过程中发现了BUG,也需要创建新的分支,在新的分支中进行。

    基于特定主题的作业在特性分支中进行,主题完成后再与master分支合并。只要保持这样一个开发流程,就能保证master分支可以随时供人查看。这样一来,其他开发者也可以放心大胆的从master分支创建新的特性分支。

  • 主干分支

    主干分支是特性分支的原点,同时也是合并的终点。一般来说是以master分支作为主干分支。

  • git merge合并分支

    现在假设我们前面创建的test分支以及实现完毕了,想要将它合并到主干分支master中,首先切换到master分支上:

    1
    git checkout master

    然后合并test分支。为了在历史记录记录下来本次分支合并,需要创建合并提交。**因此,在合并时加上--no-ff参数**。

    1
    git merge --no-ff test

    然后编辑器弹出,添加一定的描述,关闭编辑器后,就完成分支合并了。

  • git log --graph——以图表形式查看分支

    git log --graph命令查看,能很清楚地看到很多信息。

    image-20220205160840715

更改提交的操作

git reset——回溯历史版本

在前面基础操作,以及可以在实现功能后进行提交,累积提交日志作为历史记录,借此不断培育一款软件。

Git的另一特性是可以灵活的操作历史版本,借助分散仓库的优势,可以在不影响其他仓库的前提下对历史版本进行操作。

现在进行版本回溯,然后再创建一个B特性分支,Git目前结构如下:

image-20220205165541460

我们需要先回溯到分支A 的之前的分支节点,然后创建一个名为B的特性分支。要让仓库的HEAD,暂存区,当前工作树回溯到指定状态,需要使用git reset --hard命令。只要提高目标时间点的哈希值。就可以完全恢复到该时间点状态。命令示例:

1
2
# 需要注意的是,每个哈希值不一样,操作的时候看自己的哈希值
git reset --hard a369bfe343a6ce72d75b39db804da6f46769be2d
image-20220205171050472

现在来创建新分支B,并且在新分支中,在ReadMe.md文件中添加一些文字,然后提交ReadMe.md文件。

image-20220205171518879 image-20220205171548568

现在分支状态如下:

image-20220205171825806

现在需要恢复到分支A合并后的状态,使用git log只能查看当前状态为终点的历史记录。所以需要使用git reflog命令,查看当前仓库的操作日志。在日志中找出回溯历史之前的哈希值,然后通过git reset --hard命令回溯到历史之前的状态。

image-20220205172249850

从日志里可以看到我们所有的操作,只要不进行Git的GC(Garbage Colloection,垃圾回收)就可以通过日志随意调取历史状态。可以了解到我们要恢复的时间点哈希值为:7092c19b410daac6937a906966b78565e6432771,其实我们只需要输入前面的7092c19即可执行恢复。

image-20220205172957748

恢复后的状态:

image-20220205173422425

消除冲突

现在只需要合并分支B就可以满足我们想要的状态:

image-20220205173630138

执行合并后:

image-20220205174155577

这是,系统告诉我们ReadMe.md合并过程中发生冲突(Conflict)。系统在合并ReadMe.md文件时,分支A更改的部分和分支B更改的部分发生了冲突。

所以需要解决冲突来完成合并。

  • 查看冲突部分并将其解决

    使用编辑器打开ReadMe.md查看文件,会发现文件的内容变成如下示例类似样子:

    image-20220205174526571

    ==========以上的部分是当前HEAD的内容,以下的内容是要合并的分支B中的内容,现在将其该成我们想要的样子

    image-20220205174715520

    现在内容被修改成了我们想要保存的样子,冲突解决

  • 提交最后的解决方案

    现在来执行git add命令和git commit命令来完成提交

    image-20220205174916848

git commit --amend——修改提交信息

要修改上一条提交信息,可以使用git commit --amend命令,执行该命令后,会弹出编辑器,对内容进行修改后,关闭编辑器即可完成修改。

image-20220205181447963

git rebase -i——压缩历史

在合并特性分支之前,如果发现已提交的内容中有些许拼写错误等,不妨提交一个修改,然后将这个修改包含到前一个提交之中,压缩成一个历史记录。

现在我们新开一个分支C并且修改其中的内容来作为后面修正。

image-20220205183834327

然后提交故意修改错误的内容,可以通过如下命令来一次执行到暂存区再到commit

1
git commit -am
image-20220205184112266
  • 修正拼写错误

    现在来修正刚刚的错误,修正完毕后查看修正前后的差别。

    image-20220205184435866

    然后进行提交

    image-20220205184620781

    这种后来进行修正的错误,我们不希望在历史记录中看到这类提交,因为健全的历史记录不需要它们。如果在第一次合并中就发现并修正这些错误,也就不会出现这类提交了。

  • 更改历史

    因此,我们需要来更改历史。将修正的内容与之前的提交合并,在历史记录合并为一次完美的提交。因此,我们需要使用git rebase命令:

    1
    git rebase -i HEAD~2

    用上述命令,可以选定当前分支包含HEAD(最新提交)在内的两个最新历史记录为对象,并在编辑器中打开。

    image-20220205193525354

    将6aca246的修正错误历史记录压缩到92d473d的ReadMe.md中,按照下述的方法,将6aca246左侧的pick部分删除,改为fixup

    1
    2
    pick 92d473d ReadMe.md
    fixup 6aca246 修正了部分错误

    修改完成后,关闭编辑器,系统提示Rebase成功:

    image-20220205193849481

    也就是说将两个提交作为对象,合并到了第一次提交的时候成了一个新的提交。

    现在再查看提交日志会发现最新的提交日志已经合并一个切之前写的描述信息也已经不见了,这证明提交已经被修改了。

    image-20220205194109469
  • 合并到master分支

    现在将分支C可以合并到主分支master了。

    image-20220205194338635

推送至远程仓库

Git是分散型版本管理系统,前面的操作都是针对单一本地仓库的操作。现在来尝试对远程仓库进行操作。现在首先在GitHub上创建一个仓库,不需要勾选README选项。仓库名称随意,此处演示远程仓库名称为TestGit。

git remote add——添加远程仓库

**在GitHub上创建的仓库路径为”git@github.com:用户名/仓库名称.git”**。现在来使用git remote add命令将其设置成本地仓库的远程仓库。

1
2
3
4
# 命令格式:
git remote add <shortname> <url>
# 命令示例:
git remote add test git@github.com:L-Seraphine/TestGit.git

根据上述命令,将地址为:git@github.com:L-Seraphine/TestGit.git远程仓库的名称设置为:test(标识符)。可以通过标识符快速表示该地址的远程仓库

git push——推送至远程仓库

  • 推送至master分支

    如果想将当前分支下本地仓库中的内容推送给远程仓库,需要用到git push命令。现在假定我们在master分支下进行操作。

    1
    git push -u test master

    执行如上命令,当前分支的内容会被推送给远程仓库TestGit的mastet分支。-u参数可以在推送时,将远程仓库的master分支设置为本地仓库当前分支的upstream(上游)。添加这个参数,将来运行git pull命令从远程仓库获取内容时,本地仓库的这个分支就可以直接从远程仓库的master分支获取内容,省去了另外添加参数的麻烦。

    image-20220205205105145

    执行该操作后,当前本地仓库的master分支的内容就会被推送到GitHub的远程仓库中。

    image-20220205205151846
  • 推送至master以外的分支

    除了master分支之外,远程仓库也可以创建其他分支。现在我们在本地仓库创建分支D,并将它推送至远程的同名分支。

    image-20220205205512167

    现在,在远程仓库的GitHub页面就可以看到分支D了。

    image-20220205205548606

从远程仓库获取

上部分我们将GitHub上的远程仓库push了分支D。现在,所有能够访问这个远程仓库的人都可以获取分支D并加以修改。

git clone——获取远程仓库

git clone命令在很早之前就使用过,语法格式和操作方法不再说明。

image-20220205210223590

执行git clone命令后默认我们会处于master分支下,同时系统会自动将origin设置为该远程仓库的标识符。也就是说,当前本地仓库的master分支和远程仓库的master分支的内容是完全一致的。

我们使用git branch -a命令查看当前分支的相关信息。添加-a参数可以同时显示本地仓库和远程仓库的分支信息。

image-20220205210716346

获取远程仓库的分支D

使用如下命令将指定获取远程仓库的分支:

1
2
3
4
git checkout -b D origin/D
# D的意思是创建本地分支名称
# origin是默认的远程仓库的标识符
# origin/D是远程仓库的分支D的路径

现在假定我们是另一个开发者,需要做一个新的提交。在ReadMe.md文件中添加一行文字,如下:

image-20220205211918862

然后提交即可:

image-20220205212058588

然后远程推送即可:

image-20220205212202257

git pull——获取最新的远程仓库分支

当远程仓库的内容更新了,需要拉取,则可以通过如下命令:

1
git pull 远程仓库标识符 分支名称

来获取远程仓库,对本地仓库进行更新。