Git&Github的初学者教程

git
Tutorial
本文是我在学习廖雪峰的Git教程的一些笔记与个人理解
发布于

2026年8月25日

本文是我在学习廖雪峰的Git教程的一些笔记与个人理解,如果想要学的更基础更细可以前往廖雪峰的Git教程

Git与Github是什么

Git

在开始学习 Git 之前,我们先思考一个实际的问题。

假设你正在开发一个网站。刚开始的时候,项目只有一个简单的首页,你觉得很满意,于是继续添加新的功能。几天之后,你增加了登录功能,又修改了页面样式,还尝试加入了一些新的交互效果。

但是某一天,你发现项目出现了问题:某个功能突然无法使用,而你已经修改了很多文件,完全不知道是哪一次修改导致的。

这时候,你可能会想:“有没有一种方法,可以记录每一次修改,并且让我随时回到之前正常的版本?”

这就是 Git 要解决的问题。

如果你玩过 Steam 游戏,可以把 Git 理解成游戏中的存档系统。游戏中的存档可以记录你当前的进度,当你做出了错误选择或者遇到无法解决的问题时,可以读取之前的存档重新开始。例如下图:

Git 的作用也是类似的。

在开发过程中,每当你完成一个重要阶段,都可以保存当前项目的状态。这个保存过程在 Git 中叫做 commit(提交)。不同的 commit 就像不同时间创建的游戏存档,它们记录着项目在不同阶段的状态。

因此,简单来说:Git 是一个帮助我们记录代码变化、管理项目历史的版本控制工具。以官方的定义来说,Git是目前世界上最先进的分布式版本控制系统。

不过,Git 有一个特点:它主要运行在你的本地电脑上。也就是说,你可以在自己的电脑中使用 Git 来管理项目,即使没有网络也没有问题。

但是,这里又产生了问题:如果电脑损坏怎么办?如果想换一台电脑继续开发怎么办?如果想让别人看到你的代码,或者和其他人一起开发怎么办?

这时候,就需要 GitHub。

Github

GitHub 可以理解为 Git 的“云端仓库”。如果把 Git 比作电脑中的游戏存档,那么 GitHub 就类似 Steam 的云存档功能。如下图:

游戏存档原本只保存在你的电脑里,通过云同步之后,你可以在另一台电脑继续游戏。Git 也是一样。你可以把本地 Git 仓库上传到 GitHub,让代码保存在远程服务器中。

很多初学者会认为 GitHub 就是 Git,其实它们的关系更像是“工具”和“平台”的关系。

Git 是负责管理版本的工具,它关注的是:“我的代码经历了哪些变化?”.而 GitHub 是提供远程存储和协作功能的平台,它关注的是:“如何让这些代码可以被保存、分享,并且让多人一起开发?”

你完全可以只使用 Git,而不使用 GitHub。 但是,如果你希望把项目上传到网络,让其他人访问或者参与开发,那么 GitHub 就非常有用了。

在实际的软件开发过程中,Git 和 GitHub 通常会配合使用。

一个典型流程是这样的:你首先在电脑上创建项目,然后使用 Git 保存每一次重要修改。当项目达到一定阶段后,把代码上传到 GitHub。之后,如果团队成员需要参与开发,他们可以从 GitHub 获取项目,并继续修改。

所以,在开始学习 Git 的时候,你只需要先记住三个概念:

  1. Git 是一个版本控制工具,负责记录代码的变化。

  2. GitHub 是一个代码托管平台,负责保存 Git 仓库,并提供协作能力。

  3. 两者结合起来,可以让我们更加安全、高效地管理代码。

接下来,我们会从安装 Git 开始,让你的电脑真正具备代码版本管理能力。

Git安装与配置

Git安装

本文此处仅介绍windows版本的安装方法。如果是别的系统,请前往廖雪峰的Git安装教程

方法一:直接从Git官网下载安装程序,然后自行选择选项安装即可。

搜索Git - https://git-scm.com/ -> “Install for Windows”

点进后,显示如图页面,直接点击箭头处内容下载即可。默认选择默认安装路径即可,如若想更改路径,务必使用英文路径

默认编辑器我们推荐选择VSCode:

分支部分推荐改为main:

后续建议直接选择默认选项即可,如果想要知道其选项具体含义,可以前往以下网站:

安装git bash选项推荐

一般来说,我们只要在开始菜单或者桌面找到Git Bash,点击后蹦出一个如图的命令行窗口,就可以说明Git安装成功了。

Github账号注册

我们需要一个Github账号,将其与我们的Git绑定,这样我们就可以进行我们项目上传了。

首先先进入Github,如果进不去,可以使用Watt toolkit这类软件,如果有更好的就更好了,这边就不做推荐了:)

然后自己进行注册即可。下图就是已经注册成功,你可以自己改动一些个性化内容:

配置Git

我们安装完Git的第一步,就是给我们记录一个名字和Email地址:

git config --global user.name "Your Name"
git config --global user.email "email@example.com"

其中的名字和邮箱按理说可以随意取,但是如果你想关联到自己的Github账号的话,将邮箱与Github的邮箱保持一致。

且后面想要查看自己的信息,可以使用:

git config --global --list

后面想要更改名字与邮箱再设置一次即可,但是修改后只影响未来的新commit。以前已经提交的 commit 里的作者信息不会自动改变。

注意git config命令的--global参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。只要进入对应文件夹,使用前面的设置代码即可。

如何更新版本库

工作区、暂存区、版本库

在这部分,我们先介绍一下工作区、暂存区、版本库这三个概念:

  1. 工作区(Working Directory): 就是你在电脑里能看到的目录,比如我的My_site文件夹就是一个工作区:

  2. 版本库(Repository): 工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。

    什么是版本库呢?版本库又名仓库(Repository),你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。

    Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支main(一般来说是master,此处是main是因为我们前面安装Git的时候设置的),以及指向main的一个指针叫HEAD。

我们可以简单的理解为,我们本地库的内容,会通过Git上传到版本库的暂存区,即stage,然后再转移到我们指针HEAD指着的分支,默认为main

使用命令行进行一次提交

此部分我们以流程来介绍:

  1. 创建空目录

    先选择一个合适的地方,创建一个空目录,可以手动建立,也可以进入某个目标文件夹使用mkdir xxxx

    然后使用cd xxxx进入我们新建的文件夹。

    可以使用cd,显示当前目录。

    此处我们新建文件夹learngit_1:

  2. 把目录变成Git可以管理的仓库

    我们可以通过git init来执行这个事情,输入即可,输出如下:

    瞬间Git就把仓库建好了,而且告诉你是一个空的仓库(empty Git repository),细心的读者可以发现当前目录下多了一个.git的目录,这个目录是Git来跟踪管理版本库的,没事千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了。

    如果你没有看到.git目录,那是因为这个目录默认是隐藏的,用dir /a命令就可以看见

  3. 更改并上传文件库内容

    现在,我们可以编写一个readme.txt文件,内容随意。

    一定要放到learngit_1目录下(子目录也行),因为这是一个Git仓库,放到其他地方Git再厉害也找不到这个文件。

    和把大象放到冰箱需要3步相比,把一个文件放到Git仓库只需要两步。

    第一步,用命令git add告诉Git,把文件添加到仓库:

    一般来说,这个指令是git add readme.txt,但是如果我们想要直接上传该文件夹内的全部修改,可以使用git add .。

    执行上面的命令,没有任何显示,这就对了,Unix的哲学是“没有消息就是好消息”,说明添加成功。

    第二步,用命令git commit告诉Git,把文件提交到仓库:

    简单解释一下git commit命令,-m后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。

    嫌麻烦不想输入-m “xxx”行不行?确实有办法可以这么干,但是强烈不建议你这么干,因为输入说明对自己对别人阅读都很重要。

    git commit命令执行成功后会告诉你,1 file changed:1个文件被改动(我们新添加的readme.txt文件);1 insertions:插入了一行内容(readme.txt有一行内容)。

这样我们就完成了一次从本地库到版本库的上传全过程。

使用 VSCode 来进行一次提交

上面的指令有的人并不想记,这时候我们就推荐例如VSCode的这类可视化工具,我们只需要点按钮即可完成与代码一致的效果。

  1. 创建空目录

    相同的,我们也要新建一个文件夹来解释我们的流程,此处新建的文件夹为learngit_2:

  2. 把目录改成Git管理的仓库 我们可以根据下图的箭头,点击源代码管理按钮进入此界面,然后点击初始化仓库,这一步与我们前面的git init的效果完全一致:

  3. 更改并上传文件库内容

    现在我们同样可以编写一个readme.txt文件,内容同样随意,例如此处我们填写如下:

    我们可以看到,我们的源代码管理按钮右下角出现了一个数字”1”,这代表了现在的本地库与版本库内容存在一处差异。“未追踪”的含义是,这个文件是新建的,这些内容推荐读者多尝试一下,就能理解其含义了。

    这之后我们就可以点击源代码管理按钮,进入界面以后,如下图所示:

    我们可以看到更改的内容,红框处的内容等价于我们commit后面跟着的备注,提交可以等价于git add .+git commit,我们只需要点击一次就可以上传成功了,非常方便。

版本管理

查看修改与历史

查看修改

我们首先介绍一下git status这个指令,可以查看在你上次提交之后是否有对文件进行再次修改,我们在我们的learngit_1文件夹修改一下我们的readme.txt文件,且再新建一个文件test.txt,看看输出结果是怎么样的呢:

可以看出,如果我们还没有把我们的本地库内容放入暂存区(stage),现在显示的是红色的文件,且它会显示我们是修改哪些文件。如果我们使用git add . 以后,再使用git status呢:

我们可以看出来,文件名变成绿色了,它会告诉你,哪些是修改的文件,哪些是新的文件。这时候,我们再使用git commit之后呢?我们再看看结果:

Git告诉我们当前没有需要提交的修改,而且,工作目录是干净(working tree clean)的。

上面的内容是通过命令行来观察的,我们要怎么使用VSCode来查看呢?非常简单,我们直接点击源代码管理后,如图框选的内容就是我们使用git status后能看到的信息,后面的点就是来解释这个文件是什么状态:

这个指令就介绍到这,非常的简单且基础。

比较差异

此处我们介绍指令git diff, 是 Git 中一个非常重要的命令,用来查看代码或文件的差异(difference)。简单来说,它告诉你:“哪些地方被修改了?修改前是什么?修改后是什么?”

我们先基于我们的learngit_1来简单介绍一下,如果我们同时修改了readme.txt与test.txt文件,使用git diff:

通过此处我们就可以很好的看清楚,我们文件的修改内容是什么了。如果我们add以后呢:

我们可以看到什么都没有了,我们一般来说,git diff 默认看还没git add的修改,git diff --staged 看已经 git add、准备 commit 的修改,git diff HEAD 看从上一次 commit到现在的全部修改。

在VSCode中,我们可以直接点击更改的文件,其可以自动查看差别:

假如我们以及commit以后,我们使用git diff -staged就能看我们暂存区与我们版本库之间的差别了:

可以看出,完全没有差别,我们有一个结论:commit 后,暂存区会更新为新的 HEAD 状态,因此暂存区和最新提交保持一致,不再存在等待提交的修改。暂存区不会保留“未提交的变化”,但仍然保存当前提交对应的文件索引。所以我们才能得到这个指令输出结果:)

确实会很绕啊:(

查看历史

此部分我们介绍我们的指令git log,其是 Git 中最常用的命令之一,用于查看提交历史记录。它可以显示提交的详细信息,包括提交哈希值、作者、日期和提交信息。通过添加不同的参数,可以自定义输出格式、筛选条件等:

我们就可以看到我们三次commit的历史信息了:

git log命令显示从最近到最远的提交日志,如果嫌输出信息太多,看得眼花缭乱的,可以试试加上–pretty=oneline参数:

在VSCode中,我们可以查看此部分内容:

其可以显示我们现在分支指的方向,以及所有我们在git log中可以获得的信息。

恢复,撤销与回退

在前面的学习中,我们已经知道 Git 可以帮助我们保存项目的多个版本。但是在实际开发过程中,我们经常会遇到一些问题:

  1. 刚修改的代码发现写错了,想恢复之前的内容;
  2. 已经 git add 的文件,不想提交了;
  3. 已经 commit 的版本出现问题,想回到之前的版本;
  4. 删除的文件想找回来;
  5. 想查看某个历史版本的代码内容。

这些操作都属于 Git 中非常重要的一部分。不同阶段的问题,需要使用不同的命令解决。

  1. 你本地修改了一个文件,但是发现改错了: 例如,你原本的内容是Hello Git,现在改成了Hello World,但是在add之前,你突然觉得还是原来版本更好,但是现在以及改了,这时候你希望放弃当前修改,恢复到上一次 commit 的状态

    可以使用git restore, 其修复的是工作区文件。也就是说,它只能恢复没有提交的修改。

    我们使用我们的learngit_1中的readme.txt文件,我们修改且保存如下:

    但是我们这个时候后悔了,其中的this is forth sentence for learngit_1是新增的内容,我们现在其实很方便的就可以回溯,但是只是做一个简单的案例,此时我们可以使用git diff看到我们的差别,然后直接使用git restore,结果如下图:

    可以看出来git restore与git add后面的含义一致,我们直接使用git restore .即可。我们现在回learngit_1中的readme.txt文件:

    我们可以看到,神奇的恢复了!

    在VSCode中即更简单,我们可以直接放弃文件的更改即可,按钮如图:

  2. 如果修改了文件且add了,突然发现不应该提交这个文件,怎么办?

    这时候不能使用git restore <file>因为他会直接恢复文件内容,这样我们修改后的内容也不见了,正确的应该是git restore --staged <file>,这个操作非常安全。

  3. 删除后的文件的恢复

    例如我们的文件夹learngit_1 ,我们删除其中的文件test.txt,可以使用git status查看其状态:

    可以看到,该文件已经被删除了,我们分以下两种情况:

    一.还没有提交commit,我们可以使用git restore <file>:

    即从上次commit中恢复文件。

    二.我们已经提交了删除,即已经commit了,此时我们可以查看历史log,假设我们想恢复的文件在版本commit-1,其哈希值为abc123…,我们可以使用git checkout abc123 -- <file>或者git restore --source abc123 <file>来恢复文件

  4. 查看某个历史版本的代码

    我们可以使用log查看commit对应的哈希值以后,使用git show commit-id,Git会显示修改时间,作者,以及内容等。如:

  5. 版本回退

    我们现在想要的是将项目直接回到过去,可能我们现在的项目历史是:A->B->C->D,我们发布D以后发现,D出现了很大的错误,需要重新回到C,这就是版本回退。

    我们一般可以使用git reset --hard commit_id来进行版本回退。其中有三种模式:

    1. soft:其只移动HEAD,但是保留了工作区和暂存区,适合重新整理commit,他不会影响我们现在本地的内容。

    2. mixed(默认):移动HEAD并清空暂存区,但是保留了文件修改的内容。

    3. hard:将HEAD,暂存区与工作区全部恢复为想要的版本,效果最彻底。

    当然,Git必须知道当前版本是哪个版本,在Git中,用HEAD表示当前版本,也就是最新的提交,上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100。

    如果我们以及回到了以前的版本,再使用git log,就看不到此commit后的信息了,好比你从21世纪坐时光穿梭机来到了19世纪,想再回去已经回不去了,怎么办呢?

    办法还是有的,我们只要找到我们想要回去的那个commit的commit_id即可,如果找不到了,可以试试git reflog,这个是查看命令历史的指令。

    实际上,版本回退的本质是指针HEAD指的对象变换了一下而已:

  6. 撤销修改

    假如你在readme.txt中修改了一些内容,但是在准备add之前发现了有明显的错误,这时候我们可以丢弃了工作区的修改内容,即使用git checkout -- <file>

    命令git checkout – readme.txt意思就是,把readme.txt文件在工作区的修改全部撤销,这里有两种情况:

    一种是readme.txt自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;

    一种是readme.txt已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。

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

    但是如果以及git add到暂存区了,但是还没有commit,这时候我们可以使用git reset HEAD <file>来把暂存区的修改撤销掉(unstage),重新放回工作区.当我们用HEAD时,表示最新的版本。

    总结如下:

    场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file。

    场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD <file>(默认为mixed模式),就回到了场景1,第二步按场景1操作。

    场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。

  7. 删除文件

    如果你使用del删除了某文件,这个时候,Git知道你删除了文件,因此,工作区和版本库就不一致了,git status命令会立刻告诉你哪些文件被删除了: ```bash $ git status On branch master Changes not staged for commit: (use “git add/rm …” to update what will be committed) (use “git checkout – …” to discard changes in working directory)

     deleted:    test.txt

    no changes added to commit (use “git add” and/or “git commit -a”) 现在你有两个选择,一是确实要从版本库中删除该文件,那就用命令`git rm`删掉,并且`git commit`:bash $ git rm test.txt rm ‘test.txt’

    $ git commit -m “remove test.txt” [master d46f35e] remove test.txt 1 file changed, 1 deletion(-) delete mode 100644 test.txt ``现在,文件就从版本库中被删除了。(tips:先手动删除文件,然后使用git rm 和git add `效果是一样的。)

    另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本git checkout -- test.txt 。git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。

分支管理

分支(Branch)

在前面的学习中,我们已经掌握了 Git 最基础的功能:创建版本(commit),查看历史(log),恢复和回退版本

但是,到目前为止,我们的项目一直沿着一条路线向前发展.这样的设置导致每一次修改都会直接影响当前项目。

这种方式在个人练习项目中没有太大问题,但是在真实的软件开发中,经常会遇到这样的情况,正在开发一个网站,目前版本已经可以正常运行。

这时候,你想增加一个新的功能。如果直接在当前版本上修改,可能会出现新功能开发失败;原来的正常代码被破坏;想回到之前版本比较麻烦。

有没有一种方法,可以在不影响当前项目的情况下,尝试新的开发方向?这就是 Git 的 Branch(分支)。

在Git中,一个项目常常有一个默认的主要分支,以前默认是master,但是我们现在大多数项目都改为main,这也是为什么我们前面要改设置的原因,总得追逐大流:)

main常常代表的是正式版本的文件,其是稳定的,可以发布的。

廖雪峰的教程中:

每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支。HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是当前分支。

一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点:

每次提交,master分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长。

当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:

Git创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化!

不过,从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:

至于合并部分我们后面再讲。

我们要如何查看我们现在有哪些branch呢?可以使用git branch来查看:

其中的*表示当前所在分支。图中表明我们现在正在main分支。

如何创建新的分支呢?可以使用git branch 分支名,即可,我们因为已经创立了test分支,就不做演示了。

假设我们的main分支原本有A->B->C的历史,那么此时test也拥有相同的历史。但是注意,这时候并不会主动切换到我们新建的分支,还需要以下指令git switch 分支名,例如:

这样切换到test分支,之后修改代码,就不会影响到我们的main分支了.这就是分支隔离。

在实际开发中,我们更常用git switch -c 分支名这一串指令,其代表了创建与转化分支这两个功能.

值得一提的是,我们前面说的git checkout -- <file>这个指令,如果没有了--,且后面跟着的是分支名,其也能起到转化分支的功能,即switch,要创建与转化同时进行的,即git checkout -b 分支名即可.

在这我们推荐使用switch指令,防止我们忘记了checkout的撤销修改的功能

我们来试试,如果我们在test分支上修改,会对我们的main分支有什么影响吗?如下图我们对我们的readme.txt文件做了一定的修改。

此时我们add+commit,再切换回我们的main分支,发现,前面修改的内容都没了!

因为那个提交是在test分支上,而main分支此刻的提交点并没有变:

注意,我们这时候可以观察到,切换分支的时候,我们本地库的内容也会跟着变,即切换分支不仅改变 HEAD 指向,还会把该分支对应的 commit 内容检出(checkout)到工作区。

因为切分支需要修改工作区内容。如果你的工作区里已经有自己没提交的修改,而目标分支又需要改这些文件,Git 就可能覆盖你的修改。因此 Git 会阻止切换,并提示类似:

Your local changes would be overwritten by checkout

以上是我们对分支的一些初步理解与使用,下面我们开始讲如何把分支合并到main中,增加我们的新功能.

分支合并与冲突

在上一部分中,我们已经知道了 Branch 的作用:在不影响主分支的情况下,创建一条新的开发路线。

当功能开发完成并测试没有问题之后,我们通常希望把这些修改重新放回 main 分支。这个过程就叫做分支合并(merge)

简单来说,Branch 负责把不同工作分开,Merge 负责把已经完成的工作重新组合到一起。

我们来简单的介绍一下merge是什么

假设目前项目的状态如下:

我们可以看出,main分支停留在了C,而test分支已经完成了D和E两次提交.如果此时我们想要开发的功能已经完成了,我们就希望让main也包含这些修改.

我们首先要切换到要接受修改的分支,git switch main,然后执行git merge test即可:

如图中显示一般,我们是Fast-forward的合并方式,Git告诉我们,这次合并是“快进模式”,也就是直接把main指向test的当前提交,所以合并速度非常快。

即因为创建test之后,main没有再继续产生任何新的提交,Git会发现,我们只要把我们的指针从C前移到E即可,这种合并叫做快进合并.它没有真正创建一个额外的“合并提交”,而只是让 main 指向了新的位置。

但是,如果我们创建了test之后,main分支也进行了其他的修改呢?例如你在搞A功能,同事在搞B功能,他比你先搞好了,然后修改了main,这时候我们要怎么办?

我们同样画一个简单的历史图:

这时候两个分支已经真正产生了不同的历史.如果现在切换到git switch main,然后再git merge test.Git 会尝试把两边的修改组合起来.如果两个分支修改的是不同内容,Git 通常可以自动完成。最终产生一个新的版本,像一个环一样.

这边我们先理解一下,merge到底在合并什么?

实际上,Git并不是把两个文件夹合并到一起,而比较的是两个分支从共同祖先开始分别发生了哪些变化。以我们的历史图,main与test分支的共同祖先是C,Git会比较,从C->E发生了什么变化,从C->G又发生了什么变化,然后尝试把两边的修改组合起来。如果两边修改的是不同文件,通常非常简单。我们只需要把不同文件都改成修改后的版本即可:)

Git 很容易判断这些修改互不干扰,因此可以自动合并。即使修改的是同一个文件,只要修改的是不同位置,Git 很多时候也可以自动处理。

真正麻烦的是,两个分支修改了同一个文件中的同一部分内容,这时候就可能产生 Merge Conflict(合并冲突)。

它出现的原因非常简单:Git 无法判断两份修改到底应该保留哪一个。

我们这边举一个很简单的例子,假如C处,我们原本的readme.txt中最后一行是eat apple,经过更新,我们在main分支的E处修改这行为eat red apple,但是在test的G处,修改为eat green apple,现在两个分支都修改了同一行。

如果这时候执行git merge test,Git 就会遇到一个问题,我要选择E处的修改呢,还是G处的修改呢?Git 不知道。因此它不会擅自帮你决定,而是暂停合并,把决定权交给开发者.

我们下面来亲手制造一个Merge conflict,看看会发生什么

我们对我们的learngit_1文件夹中的readme.txt做以下修改,对于main分支,其最后一行修改为this is test for merge conflict;而test分支,其最后一行修改为this is test 4 merge conflict. 我们将test分支合并到main分支试试:

可以看到Git 自动合并失败了,需要你手动解决冲突

此时我们可以使用git status查看有哪些文件存在冲突,我们这样才有目标:

可以看到,Git说readme.txt都修改了,并且 Git 无法自动决定结果,这时候我们打开readme.txt文件看看。

这几行就是 Git 自动插入的冲突标记。

Git 的冲突内容通常由两个部分组成:

  1. <<<<<<< HEAD 与 ======= 之间的部分:这段内容是现在这分支中存在的冲突内容。

  2. ======= 与 >>>>>>> 分支名 之间的部分:这段内容是将要合并进来的分支中存在的冲突内容。

Git 实际上是在问你,这两个版本我都找到了,你决定最终应该长什么样

此时我们只能手动解决冲突,我们自己选择保留哪一种内容,可以是main分支,也可以是test分支,甚至可以是你自己重新写的内容,解决冲突并不是“选择左边还是右边”,而是确定这个文件最终应该是什么样子。

例如我们决定保留main分支下的内容,那么把冲突标记和另一边内容全部删除,最终文件中冲突部分只留下this is test for merge conflict即可。

后面直接使用git add加git commit即可。这样整个 merge 才真正完成。

值得注意的是,此部分同样可以使用VSCode,此处就不做介绍了,非常简单,自己尝试一下,一试就会:)

如图,我们就可以看到我们的全过程图

一般来说,现在test分支就已经没用了,我们可以直接删除即可,这也是一般的工作流程,指令是git branch -d 分支名,-D表示为强硬删除

如果你在看到存在大量的merge后不想要继续merge怎么办,可以使用git merge --abort

如果想要学习更多的分支内容,请前往廖雪峰的分支管理教程

Github

远程仓库:Remote、SSH、Push 与 Pull

到目前为止,我们学习的 Git 操作基本都发生在自己的电脑上。无论是 commit、版本回退,还是创建和合并分支,本质上都只是在操作本地的 Git 仓库。

但是我们最开始介绍 GitHub 的时候提到过,GitHub 可以理解为 Git 仓库的“云端”。如果我们希望把自己的项目保存到 GitHub,或者和其他人共同开发项目,就需要让本地 Git 仓库与 GitHub 上的远程仓库建立联系。

这个过程其实可以简单的理解为:本地Git仓库push到Github远程仓库,而我们可以从Github远程仓库pull到本地的Git仓库。

其中,本地仓库和 GitHub 仓库之间的关系由 remote 管理;SSH 用来证明“你是谁”;push 负责把本地提交上传到远程仓库;pull 则负责把远程仓库中的新内容同步回来。

Git 中把 GitHub 上这样的仓库称为远程仓库(Remote Repository)。

需要注意,远程仓库并不一定非要位于 GitHub。GitHub 只是目前非常常见的 Git 托管平台,其他 Git 服务器同样可以作为 Remote。

不过本文使用 GitHub,所以后面提到远程仓库时,我们基本都指 GitHub 上的仓库。

在 GitHub 创建一个远程仓库

首先,登录我们的Github账户,点击右上角的+,选择创建一个新的Repository:

Repository名称可以随意填写,只要你觉得可以帮助你认清是哪个仓库即可,仓库还可以选择Pubulic或者Private,Public 表示其他人可以看到这个仓库,Private 则表示只有你自己以及被授权的用户可以访问。

因为我们本地已经存在 learngit_1 仓库,所以这里建议暂时不要勾选自动创建 README、.gitignore 或 License,直接创建一个空仓库即可。这样可以避免本地仓库和 GitHub 仓库一开始就拥有不同的提交历史。

一般可以看到两种常见形式HTTPS和SSH。

两种方式都可以连接 GitHub。本文主要使用 SSH,因此在真正连接远程仓库之前,我们先把 SSH 配置好。该图中下方的两块代码分别代表着,已经有本地库与还没有本地库的情况,我们选择下者,配置好SSH后直接输入对应代码即可。

SSH:让 GitHub 知道“你是谁”

假设你在自己的电脑上执行git push,告诉 GitHub,我要上传这个仓库,GitHub 怎么知道操作电脑的人真的是你?

GitHub 需要一种更加可靠的身份验证方式,这里我们使用的就是 SSH Key。

SSH Key 可以简单理解成一对互相对应的钥匙:你的电脑中存在私钥,而我们提供给Github公钥。当电脑尝试连接 GitHub 时,GitHub 可以通过这套机制确认:这个连接确实来自拥有对应私钥的用户

我们可以在我们的主文件夹看看是否存在.ssh文件夹,里面应该有两个文件id_rsa和id_rsa.pub两个文件,对应着私钥与公钥两种。如果没有的话,可以使用以下指令生成一个ssh-keygen -t rsa -C "youremail@example.com"

你需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个Key也不是用于军事目的,所以也无需设置密码。记住,生成出来的私钥不能泄露出去,而公钥可以放心地告诉别人。我们上传给Github的也是公钥。

我们可以使用VSCode等方法查看公钥的内容,然后直接复制完整内容。我们进入Settings,然后点击左侧栏Access中的SSH and GPG keys:

点击SHH keys块的右上角New SSH key, 然后输入一个你能认得的title与你的公钥即可。

我们可以使用以下指令来查看自己是否成功,如果第一条指令不行就换成下面那条,记得输入yes:

如果出现了这种情况,请你在.ssh文件夹创建一个config,不然后面会有问题哦

具体策略如下:从上到下输入:

$ cd .ssh  #(进入该文件夹)
$ echo Host github.com>config
$ echo     HostName ssh.github.com>>config
$ echo     User git>>config
$ echo     Port 443>>config

然后检查一下内容:

$ type config
Host github.com
    HostName ssh.github.com
    User git
    Port 443

然后再检查一下文件名称:

$ dir

你应该能看到一个config而不是一个文件夹,千万不要变成文件夹啊😫

需要注意的是,SSH 测试成功并不代表你真的“登录进了 GitHub 的服务器终端”。GitHub 只是通过 SSH 确认了你的身份,并不会给你提供普通的 Shell。

到这里,我们的电脑已经具备了通过 SSH 与 GitHub 通信的能力。

将本地仓库与远程仓库连接起来

SSH 配置完成以后,我们回到之前的 learngit_1。然后使用git remote add origin git@github.com:用户名/learngit_1.git:

我们先介绍一下这串指令的含义: 1. git remote 表示管理远程仓库。 2. add表示增加一个远程仓库。 3. origin 是我们给这个远程仓库起的名字 4. git@github.com:example/learngit_1.git 才是真正的远程仓库地址。 > 所以整句话可以理解成:给当前 Git 仓库添加一个名叫 origin 的远程仓库,它的地址是后面的 GitHub 地址。

至于为什么给远程仓库起origin这个名字,是大家都习惯了,你起成github也是可以的

我们可以使用git remote来查看我们的远程仓库,如果希望看到详细地址,可以使用git remote -v:

fetch表示从这个地址获取内容。 push表示向这个地址上传内容。

现在本地仓库和 GitHub 已经建立联系,但是 GitHub 仓库目前还是空的。这时候就需要push,可以把其简单理解为把本地仓库中的提交推送到远程仓库。

第一次上传的时候,可以可以执行git push -u origin main 表示为上传到哪个远程仓库,上传哪个分支的内容。所以这句话就是:把本地的 main 分支推送到 origin 这个远程仓库。其中的-u的作用是以后 Git 就知道本地 main 默认应该推送到哪个远程分支。以后我们只需要git push即可。

我们这时候刷新一下我们的Github的仓库页面:

这就代表我们已经成功啦.

我们以后想把我们的工作更新到远程仓库,其整个流程为:

修改文件->git add->暂存区->git commit->本地Git仓库->git push->GitHub远程仓库

我们前面学习过分支,那要如何把我们的分支也上传过去呢?我们同样可以执行git push -u origin test之后 GitHub 上就会出现对应的远程分支。 这样以后git push就会同时更新这两个分支的信息。

Pull:把 GitHub 的修改同步到本地

我们前面讲的是把本地的内容上传到远程仓库,肯定也要有操作可以把远程仓库的内容放到我们的本地来,这个操作就是git pull,例如有时候你的同伴已经在远程仓库完成了一次更新,你要再工作肯定是要继续新的仓库,旧的仓库就没用了。

这样就会引出了前面说的冲突问题,如果已经有人在远端更新了仓库,你这时候使用git push,Git通常会拒绝,原因很简单:远程已经有了你本地不知道的新内容。

如果 Git 允许你直接 Push,就可能覆盖其他人的工作。这时候我们通常需要先git pull,把远程变化同步下来,Git 会尝试把两边的修改进行合并。如果双方修改了同一部分内容,就可能出现上一章讲过的Merge Conflict,我们的解决方案与那部分一模一样,我们最后解决完成后git push即可。

pull是在做什么呢?

实际上,它包含了两个操作,即git fetch+git merge,前者负责把远程仓库的最新信息获取回来,但不会直接修改你当前正在工作的分支。然后 Git 再把获取到的远程变化合并到当前分支。

假如你正在修改readme.txt,但是还有没有commit,这时候进行pull,如果远程仓库恰好也修改了相关文件,就可能让情况变得比较复杂,所以我们在pull之前,最好先git status,确认一下自己的工作区状态,如果已经完成的差不多了,可以先add+commit

有时候我们想要修改我们的地址,如把SSH变成HTTPS:可以使用git remote set-url origin 新地址,但是如果要删除这个远程库链接,可以使用git remote remove origin

基于VSCode的push与pull

前面的内容我们同样可以使用我们的VSCode,这边我们做简单的介绍:

首先,我们要把本地库链接到Gitub,可以使用下图的按钮,直接使用即可,上侧输入的内容为你给远程仓库的命名,例如我们这边是origin:

以及我们的push,pull,fetch操作:

VSCode 并没有使用另一套版本控制系统,只是把这些 Git 操作变成了按钮。如果你的VSCode是英文的,应该可以更好的帮助你理解。

Clone:从远程仓库获取一个完整项目

上一部分我们已经学会了如何把本地 Git 仓库连接到 GitHub,并使用push 和 pull 在本地仓库与远程仓库之间同步提交。但是前面的操作有一个前提:本地电脑中已经存在这个 Git 项目。

如果现在换了一台电脑,或者你想参与别人的 GitHub 项目,本地连项目文件夹都没有,这时候就不能直接使用 git pull,因为本地还没有对应的 Git 仓库。这种情况下,就需要使用:git clone

clone 的作用可以简单理解成:把一个远程 Git 仓库完整地复制到本地.这里的“完整”不仅包括项目文件,还包括这个仓库的 Git 历史、分支信息以及远程仓库配置。

假如Github上已经有一个仓库:git@github.com:example/learngit_1.git

我们希望把它下载到本地,只需要在准备存放项目的位置执行:git clone git@github.com:example/learngit_1.git

我们可以直接在github中点击右上角的Code按钮,就可以获得地址了。

Git 会自动创建:learngit_1/ ,并把远程仓库中的内容下载进去。我们可以进入这个项目,然后使用git status来查看其内容。此时你已经拥有了一个可以正常使用的本地 Git 仓库。

我们可以尝试下载以下图中的仓库,这是一个Skill,感觉看上去不错:

我们可以看到,我们获得了全部的信息,这非常完美,且我们以后也可以通过pull等方法进行更新啦。

我们上面Code按钮中看到可以下载Zip,有的人会说,这不也是下载了整个文件夹吗,有什么区别?

如果我们下载Zip,你得到的只是当前版本的文件,其中是没有.git的,因此也没有这个项目的完整 Git 历史。

而使用git clone 获得的是项目文件+.git+Commit历史+分支信息+远程仓库配置。

对的,我们clone以后,Remote 已经自动配置好了:

如果你拥有这个仓库的 Push 权限,也可以直接git push上传自己的提交。

所以如果我们对于一个有权限的Github项目,如果换了一台新电脑,整个工作流程就是:

从Github中clone下来项目, 然后在本地项目进行修改, 使用git add .+git commit ,最后使用git push把新的内容推到Github中去。

如果希望 Clone 之后直接进入某个指定分支,可以使用:git clone -b 分支名 仓库地址

.gitignore:告诉 Git 哪些文件不要管理

学习到这里还有一个非常实际的问题。Git 默认会看到项目目录中的大多数文件。

但是实际开发中,并不是所有文件都应该提交到 Git 仓库。我们本地的仓库中可能有一些私密的信息,不应该随意的上传到Github中去,这时候就需要.gitignore

.gitignore 是一个普通文本文件。它的作用是告诉 Git 哪些文件或文件夹应该被忽略. 我们可以在里面输入例如node_modules/等内容,这些就代表了要忽略上传的文件。

基本写法:

  1. 如果想忽略一个文件:test.txt 那么test.txt不会被 Git 跟踪
  2. 如果想忽略一个文件夹:node_modules/ 那么整个:node_modules文件夹都会被忽略。
  3. 如果想忽略某一类文件:*.log 表示忽略所有:.log文件。例如debug.log或者error.log等。

我们在这里举一个例子,假如我们写为:

node_modules/
.env 
*.log 
temp/

表示为忽略整个node_modules与temp文件夹,.env表示忽略环境配置文件,*.log表示忽略所有日志文件。

补充: 如果想要某一类中的某一个文件是可上传的,可以使用:

*.log
!important.log

这种写法。

在这我们着重讲一个文件夹node_modules,这个文件夹往往是不会上传的,里面可能包含成千上万个依赖文件,而且体积很大。但是这些依赖通常已经记录在:package.json和package-lock.json中,clone之后,我们可以使用npm install生成自己的node_modules,因此没有必要把整个目录上传到 Git。类似的思想也适用于其他语言自动生成的文件。

.gitignore 主要作用于“还没有被 Git 跟踪”的文件。如果你已经使用add和commit进行跟踪了,已经进入版本控制的文件,不会因为后来加入 .gitignore 就自动停止跟踪。

如果我们要取消追踪可以:

现在.gitignore加入该文件,例如config.txt,然后执行git rm --cached config.txt,之后提交git commit -m "stop tracking config.txt"再git push即可。

如果是文件夹的话,可以使用git rm -r --cached node_modules,即加入-r,其含义是递归处理整个目录。之后git commit -m "remove node_modules from git"即可。

git check-ignore -v 文件名可以告诉你是哪一条 .gitignore 规则忽略了它

使用 VS Code Clone GitHub 项目

如图所示,我们可以直接获取即可,VSCode 会帮助我们执行 Clone,并询问是否打开这个项目。

团队协作

在真实的开发中,一个项目往往不会只有一个人来开发,我们可能有多个作者来分别负责一些功能,如果所有人都直接修改main分支,那么很容易出现问题,因此多人开发的时候,我们一般不会直接让所有人都直接修改main,而是开发自己的功能在分支后且push上去,这时候需要pull request,需要别人检查一下才会选择是否合并。

我们下面会详细的讲述一下多人开发的流程

假设我们在Github上有一个团队项目project,Alice 和 Bob 都拥有这个仓库的开发权限。

第一次参与项目时,他们都先会git clone git@github.com:example/project.git 从而得到了自己的本地仓库。这时候虽然大家操作的是同一个 GitHub Repository,但每个人实际上都有自己独立的本地 Git 仓库。

第一步:开始开发之前先同步 main

假设 Alice 准备开发登录功能。首先先应该进入main分支,然后同步Github中的最新内容:git pull,这一步是为了防止其本地库的main可能已经落后了。如果她一开始就是基于旧代码开发,后面更容易产生冲突。

因此一个比较好的习惯就是:

git swtich main
git pull

先让自己的 main保持最新,再开始新的工作。

第二步:创建自己的功能分支

同步完成之后,不直接修改 main。而应该创建新的分支,如git switch -c feature-login,之后Alice 就在:feature-login中开发登录功能。这里的分支名通常会尽量描述自己的任务。如果是修复 Bug,也可能写成:fix-login-error等等,原则就是看名字就应该大概知道这个分支在做什么。

第三步:正常开发并 Commit

接下来和之前学习的 Git 操作完全一样。Alice 修改代码:login.html, login.js,然后我们可以查看状态git status,确定修改git diff,然后使用git add .+git commit -m "add login page"继续开发后,还可以继续add与commit。

于是我们的历史可能会变成:

这里的 D 和 E 都只存在于 Alice 的本地 feature-login 分支中。

第四步:把自己的 Branch Push 到 GitHub

现在登录功能开发完成。但是 GitHub 还不知道:feature-login这个分支。因此第一次Push的时候,可以git push -u origin feature-login,执行后这样 GitHub 上就出现了 Alice 的功能分支。后面也可以直接git push即可。

这里会产生一个问题,为什么不直接 Merge 到 main?即直接使用:

git switch main 
git merge feature-login 
git push

如果这是你自己的个人项目,这么做完全没有问题。但是多人协作中,我们通常希望:在代码真正进入 main 之前,让其他成员检查一下修改。这个请求就是Pull Request通常简称PR.

创建Pull Request

当我们执行了git push -u origin feature-login之后,进入Github Repository 页面,其往往会自动提示Compare & pull request,点击进去就可以创建PR。

当然也可以进入Pull requests页面,然后New pull request。

接下来需要选择两个分支。[base: main & compare: feature-login]

这里的base表示为修改最终要进入哪个分支。而compare表示哪个分支提供新的修改。

Title 应该简单说明这次修改完成了什么。而 Description 可以说明具体的工作内容。Pull Request 的目的不是写很多文字,而是让负责 Review 的成员能够快速知道:“这次修改是干什么的?”

我们可以在github上直接看到如图所示内容,即改变了什么:

如果经过你的检查,我们可以通过此次更改了:

如果你觉得没通过,可以给他一些意见,让他重新修改一下。

Alice 不需要重新创建 Pull Request。她只需要回到自己的:feature-login,继续修改,然后重新add、commit、push即可。新的 commit 会自动出现在原来的 Pull Request 中。故Pull Request 是跟着分支走的,而不是只对应一次 commit。

我们在push之前,可能存在一个问题,就是多人开发时,很可能在 Alice 开发登录功能期间,main 已经继续发生变化。例如在他开发期间,bob的功能已经进入了main,且推进了两个版本了,这时候我们本地的main版本太低了。

如果双方修改的内容互不影响,GitHub 可能仍然可以直接合并。但是如果双方修改了同一个文件的同一部分,就可能出现:Merge ConflictGitHub 会提示这个 Pull Request 不能直接合并。这时候就需要先解决冲突。

我们先简单尝试一下,这时候我们在我们的本地中创建第二个分支test_2即可,我们看看我们修改相同部分以后会出现什么输出呢?

我们可以看到,已经出错了。

我们有一种比较简单的方法:

先切回git switch main;获取最新的mian:git pull;然后回到自己的分支:git switch test_2;新的 main 合并进来:git merge main,现在如果有冲突,就按照前面 Merge Conflict 一章的方法解决。修改成最终想要的结果,然后add, commit, push 即可。

这时候我们就可以成功PR了

如图,我们可以我们已经成功修改了,且存在了三个branch,如果不想要的分支,merge完可以直接删除啦。

如果没有仓库写权限怎么办?Fork

针对于我们没有操作权限的仓库,应该怎么办呢? 这时候就需要Fork

Fork 可以简单理解成:在自己的 GitHub 账号中复制一份别人的仓库。

之后,你的Github中就会出现对应的仓库了,这两个仓库是独立的,现在你可以自由的push到你自己的仓库中去。

Fork的一个基本流程:

例如你希望给一个开源项目修复 Bug。

首先,先把别人的仓库fork到我们自己仓库。然后 Clone 自己的 Fork,修改后在 GitHub 上创建 Pull Request。然后项目维护者就可以检查你的代码。如果他们认为修改没有问题,就可以把你的 PR Merge 到原项目。这就是很多开源项目接受外部贡献的方式。

如果还是没懂的话,可以前往 傻子也能做开源这个视频看看详细教程

Tag与Release :给重要版本一个名字

我们前面的学习可以知道,每执行一次commit,Git就会保存一个新的版本,随着项目不断开发,提交历史可能会越来越长。

如果现在我们想找到某个重要版本,例如:“第一个正式发布的版本”,我们前面说可以通过git log找到它对应的 commit hash。但是这种方式并不直观。我们可能更希望直接把它称为:v1.0.0这就是 Git 中 Tag(标签) 的作用

Tag 就是给某个重要 commit 起一个固定而且容易理解的名字。

前面的commit是一些普通存档,tag就是在普通存档中再选择一些重要存档。

可以使用git tag查看当前仓库已经存在的tag,如果已经存在了一定规则,我们可以查找git tag -l "v1.*"这样就可以查看所有v1.x的标签了。

我们要如何创建tag呢? 我们可以对我们现在的commit版本,直接使用git tag v1.0.0他就会认为我们现在的版本是v1.0.0

如果要给过去的版本创建tag,我们只需要知道其commit-id即可,我们使用git tag v1.0.0 commit-id即可

常见的tag分为两种:Lightweight Tag & Annotated Tag

我们前面使用的就是Lightweight Tag,它本质上是一个指向某个 commit 的名字。如果希望tag本身还包括创建者;创建时间;说明信息。

其指令为git tag -a v1.0.0 -m "Release version 1.0.0"

在正式项目中,如果 Tag 是用于软件发布,通常更推荐使用 Annotated Tag,因为它能够保存更多关于这个版本的信息。

如果你希望查看V1.0.0, 可以使用git show v1.0.0

且我们要清楚一点,tag不会自动上传到Github,因此我们需要单独 Push Tag,例如git push origin v1.0.0意思是把本地的 v1.0.0 Tag 上传到 origin远程仓库。 且可以一次push多个tag,即git push origin --tags

如果创建错了,可以使用git tag -d v1.0来删除tag。我们可以使用git push origin --delete v1.0删除远程的tag

Release是 GitHub 在 Tag 基础上提供的“正式发布页面”。

GitHub Releases 页面,一个 v1.0.0 Release 中包含版本标题、更新说明和下载文件 ,是基于某个 Tag 创建一个更加完整的发布页面。

GitHub Release 不仅可以写版本说明,还可以附加文件。这样用户进入:release之后,可以直接下载已经编译好的程序。如果没有手动上传文件,GitHub 通常也会根据 Tag 提供对应源代码的归档下载,例如:(Source code (zip)),(Source code (tar.gz))

我们如图就可以发布我们的release,记得在里面选择好版本号以及修改的内容。

一般来说版本号是v1.0.0,代表着vMAJOR.MINOR.PATCH

  1. PATCH:一般是出现了一个小bug,我们修复一下,并没有增加新的主要功能。
  2. MINOR:增加兼容的新功能,但原来的功能仍然可以正常使用
  3. MAJOR:产生较大的不兼容变化,如果软件进行了一次非常大的改版,使原有接口或使用方式发生明显的不兼容变化,就可能提升主版本号

可能后面还会跟着Alpha、Beta 和 Release Candidate,如v2.0.0-Alpha,v2.0.0-beta,v2.0.0-rc.1等等

  1. Alpha一般表示:非常早期的测试版本,功能可能还不完整。
  2. Beta一般表示:功能基本完成,但仍需要较多测试。
  3. RC:可以理解成:候选正式版本,如果没有发现严重问题,就可能成为最终版本。

这可以帮助我们理解一些开源项目,选择合适的稳定的版本其实更好哦~

Git常用命令速查

下面整理了本教程中最常用的 Git 命令。实际使用时不需要一次全部记住,只需要知道“遇到某种情况应该找哪一类命令”即可。对于不确定当前仓库状态的情况,优先使用 git status 查看,再决定下一步操作。

Git 基础配置

指令 常用情况 作用
git --version 安装 Git 后检查是否安装成功 查看当前电脑安装的 Git 版本
git config --global user.name "用户名" 第一次使用 Git 时 设置 Git 提交记录中使用的用户名
git config --global user.email "邮箱" 第一次使用 Git 时 设置 Git 提交记录中使用的邮箱
git config --global user.name 想确认当前配置的用户名时 查看全局 Git 用户名
git config --global user.email 想确认当前配置的邮箱时 查看全局 Git 邮箱
git config --list 想查看当前 Git 配置信息时 查看 Git 当前使用的各种配置

创建与查看 Git 仓库

指令 常用情况 作用
git init 已经有一个本地项目,希望开始使用 Git 管理 在当前目录创建 Git 仓库,并生成 .git 目录
git status 不确定当前 Git 状态,或准备进行 add、commit、restore 等操作之前 查看当前分支、工作区修改、暂存区内容以及未跟踪文件
git status -s 希望快速查看项目状态时 以更简洁的形式显示 Git 状态
git log 想查看项目以前的提交记录 查看 Commit 历史,包括 Commit ID、作者、时间和提交信息
git log --oneline 提交历史较长,希望快速查看时 每个 Commit 只显示一行,主要显示短 Commit ID 和提交信息
git log --graph --oneline --all 项目存在多个分支,希望观察提交关系时 用简单的图形方式显示所有分支的提交历史

添加与提交修改

指令 常用情况 作用
git add 文件名 只准备提交某一个文件 把指定文件的当前修改加入暂存区
git add . 当前项目的修改基本都准备一起提交 把当前目录及其子目录中的修改加入暂存区
git add -A 希望把新增、修改和删除全部加入暂存区 将整个仓库中的所有变化加入暂存区
git commit -m "提交说明" 暂存区内容确认无误后 把暂存区中的内容正式保存为一个新的 Commit
git commit 希望使用编辑器输入更完整的提交说明时 创建 Commit,并打开默认编辑器填写 Commit Message
git commit --amend 刚刚完成 Commit,但提交说明写错或漏掉少量文件时 修改最近一次 Commit。已经 Push 的 Commit 不建议随意使用
git diff 文件修改后,希望知道具体改了什么 查看工作区相对于暂存区发生的变化
git diff --staged 已经执行 git add,希望确认即将提交什么 查看暂存区相对于最近一次 Commit 的变化
git diff HEAD 希望一次查看所有尚未提交的修改时 查看当前工作区和最近一次 Commit 之间的整体差异

撤销工作区修改

指令 常用情况 作用
git restore 文件名 文件修改错了,而且还没有 Commit,希望直接放弃修改 把文件恢复为暂存区或最近一次 Commit 中的状态
git restore . 当前所有未提交修改都不需要了 恢复当前目录下所有可以恢复的文件。执行前应谨慎确认
git restore --staged 文件名 文件已经执行 git add,但暂时不想提交 把文件从暂存区移出,但保留工作区中的实际修改
git restore --staged . 一次 add 了很多文件,现在希望全部取消暂存 清空这些文件的暂存状态,但保留文件修改
git restore --source CommitID 文件名 希望让某个文件恢复成历史版本中的内容 从指定 Commit 中取出该文件,放到当前工作区
git restore --source v1.0.0 文件名 希望恢复某个 Tag 对应版本中的文件 将指定 Tag 中的文件内容恢复到当前工作区

需要特别注意,git restore 文件名 可能直接丢弃尚未保存的本地修改,所以在执行之前最好先使用:

git diff

确认这些修改确实不再需要。


删除文件

指令 常用情况 作用
git rm 文件名 文件不再需要,希望同时从本地和 Git 中删除 删除文件并把删除操作加入暂存区
git rm -r 文件夹 希望删除整个已经被 Git 跟踪的目录 递归删除目录并记录删除操作
git rm --cached 文件名 文件本地还要保留,但不希望 Git 继续管理 从 Git 跟踪记录中移除文件,但不会删除本地文件
git rm -r --cached 文件夹 某个目录已经被 Git 跟踪,现在想加入 .gitignore 停止跟踪整个目录,但保留本地目录
git restore 文件名 文件被误删,而且删除还没有 Commit 从 Git 中恢复误删除的文件

例如 node_modules/ 已经错误地提交过,即使后来加入 .gitignore,Git 仍然会继续跟踪它。此时可以:

git rm -r --cached node_modules

然后再 Commit。


查看与恢复历史版本

指令 常用情况 作用
git show CommitID 想查看某一次 Commit 具体修改了什么 显示指定 Commit 的作者、时间和代码变化
git show HEAD 想查看最近一次 Commit 显示当前 HEAD 所在 Commit 的详细信息
git show HEAD~1 想查看上一个 Commit 查看当前版本之前一个版本的详细信息
git switch --detach CommitID 只是想临时查看或测试某个历史版本 进入指定历史版本,但不会移动当前分支
git switch main 查看历史版本之后希望回到主分支 重新切换到 main 分支
git restore --source CommitID 文件名 只希望恢复某个旧版本中的一个文件 把指定历史版本中的文件复制到当前工作区

其中:

HEAD 可以理解为 Git 当前所在的位置。

而:

HEAD~1 表示当前 Commit 的上一个 Commit。

HEAD~2 表示再向前两个 Commit。


版本回退与撤销 Commit

指令 常用情况 作用
git revert CommitID 某个已经提交、甚至已经 Push 的 Commit 出现问题 创建一个新的 Commit,用相反修改撤销指定 Commit,不删除原有历史
git reset --soft CommitID Commit 提交得不好,希望重新整理提交,但保留暂存区内容 移动 HEAD,同时保留工作区和暂存区
git reset CommitID 希望取消后面的 Commit,但保留文件修改 移动 HEAD,并取消暂存,工作区修改仍保留
git reset --mixed CommitID 与普通 git reset 相同 移动 HEAD,重置暂存区,保留工作区文件
git reset --hard CommitID 确定不要当前版本以及本地修改,希望彻底回到旧版本 同时恢复 HEAD、暂存区和工作区,危险性较高
git reset --hard HEAD~1 希望彻底退回上一个 Commit 删除当前工作区修改,并让当前分支回到上一个 Commit

reset --hard 是本教程中需要格外谨慎的命令之一。它可能让未 Commit 的修改直接消失。

如果修改已经上传到 GitHub,并且其他人可能已经获取这些 Commit,一般更推荐:

git revert CommitID

而不是随意重写历史。


Branch 分支操作

指令 常用情况 作用
git branch 想知道当前有哪些本地分支 查看所有本地 Branch,* 表示当前所在分支
git branch 分支名 希望创建一个新分支,但暂时不切换 从当前位置创建新的 Branch
git switch 分支名 希望切换到已经存在的分支 切换当前工作分支
git switch -c 分支名 开始开发一个新功能时 创建新分支并立即切换过去
git branch -d 分支名 功能分支已经 Merge 完成,不再需要 安全删除本地分支,如果未合并 Git 通常会拒绝
git branch -D 分支名 确定不再需要一个尚未 Merge 的分支 强制删除本地分支,应谨慎使用
git branch -a 希望同时看到本地和远程分支 显示本地 Branch 和远程跟踪 Branch

实际开发新功能时,一个非常常见的操作是:

git switch main
git pull
git switch -c feature-login

也就是先更新主分支,再从最新版本创建自己的功能分支。


Merge 与冲突处理

指令 常用情况 作用
git merge 分支名 某个功能分支开发完成,希望合并到当前分支 把指定分支的修改合并进当前分支
git status Merge 发生 Conflict 时 查看哪些文件存在冲突
git add 冲突文件 手动处理完冲突以后 告诉 Git 该文件中的冲突已经解决
git commit 冲突全部解决并暂存以后 完成 Merge Commit
git merge --abort Merge 出现大量问题,希望取消本次合并 尝试恢复到执行 Merge 之前的状态

例如需要把:

feature-login

合并进:

main

应该先:

git switch main

再:

git merge feature-login

需要注意,git merge feature-login 的意思并不是“切换到 feature-login”,而是:

把 feature-login 合并进我当前所在的分支。


SSH 与 GitHub 身份验证

指令 常用情况 作用
ls ~/.ssh 配置 SSH 之前 查看电脑是否已经存在 SSH Key
ssh-keygen -t ed25519 -C "邮箱" 第一次通过 SSH 连接 GitHub 创建一对新的 SSH 公钥与私钥
cat ~/.ssh/id_ed25519.pub SSH Key 创建完成后 查看公钥内容,用于添加到 GitHub
ssh -T git@github.com GitHub SSH 配置完成以后 测试电脑是否可以通过 SSH 成功认证 GitHub

其中:

id_ed25519

通常是私钥,不应该发送给别人。

而:

id_ed25519.pub

是公钥,可以添加到 GitHub 的 SSH and GPG keys 中。


Remote 远程仓库

指令 常用情况 作用
git remote 想查看当前仓库连接了哪些远程仓库 显示远程仓库名称
git remote -v 想查看远程仓库具体地址 查看远程仓库名称以及 Fetch、Push 地址
git remote add origin 仓库地址 本地仓库第一次连接 GitHub 添加一个名为 origin 的远程仓库
git remote set-url origin 新地址 GitHub 仓库地址写错或者发生变化 修改 origin 对应的远程地址
git remote remove origin 不再需要当前 Remote 删除名为 origin 的远程仓库配置

其中:

origin

只是远程仓库常用的默认名称,并不是 GitHub 强制要求的特殊关键字。


Push 上传代码

指令 常用情况 作用
git push -u origin main main 第一次上传到 GitHub 把本地 main Push 到 origin,并建立上游关系
git push 已经配置好上游分支以后 把当前分支的新 Commit 上传到远程
git push -u origin 分支名 新建的功能分支第一次上传 把本地 Branch 上传到 GitHub,并建立远程跟踪关系
git push origin 分支名 明确指定需要上传哪个分支时 将指定分支 Push 到 origin
git push origin --delete 分支名 远程功能分支已经不再需要 删除 GitHub 上的指定远程分支

例如新建:

feature-login

后,第一次上传:

git push -u origin feature-login

以后继续提交后,只需要:

git push

即可。


Pull 与 Fetch

指令 常用情况 作用
git pull 开始工作前,希望获取 GitHub 最新代码 获取远程分支的最新提交并合并进当前分支
git pull origin main 希望明确从 origin 的 main 获取内容 获取并合并指定远程分支
git fetch 希望查看远程最新变化,但暂时不想修改当前分支 获取远程仓库的新 Commit 和分支信息,但不自动 Merge
git fetch origin 只希望更新指定 Remote 的信息 从 origin 获取最新远程信息

可以简单理解:

git fetch

负责“把远程信息拿回来”。

而:

git pull

通常可以粗略理解为:

fetch + merge

所以 pull 有可能产生 Merge Conflict,而 fetch 本身不会直接修改当前工作分支。


Clone 获取项目

指令 常用情况 作用
git clone 仓库地址 本地还没有项目,第一次从 GitHub 获取仓库 下载项目文件、Git 历史、分支信息,并自动配置 origin
git clone -b 分支名 仓库地址 Clone 后希望直接进入指定分支 Clone 仓库,并切换到指定 Branch
git clone 仓库地址 文件夹名 不希望使用默认 Repository 名作为本地目录 Clone 仓库并指定本地保存目录名称

需要区分:

git clone

通常用于第一次获取整个仓库。

而:

git pull

通常用于本地已经有仓库以后继续同步最新内容。


.gitignore 与停止跟踪文件

.gitignore 本身不是 Git 命令,而是 Git 仓库中的一个配置文件。常见规则如下:

指令 常用情况 作用
node_modules/ Node.js 项目 忽略整个 node_modules 目录
.env 项目包含环境变量、Token、API Key 等 忽略 .env 文件
*.log 项目运行时会生成日志 忽略所有 .log 文件
temp/ 项目存在临时目录 忽略整个 temp 目录
*.pyc Python 项目 忽略所有 .pyc 缓存文件
__pycache__/ Python 项目 忽略 Python 缓存目录
!important.log 已经忽略 *.log,但某个日志文件需要保留 使用 ! 重新允许某个文件被 Git 管理
git check-ignore -v 文件名 某个文件没有出现在 git status 中,不知道为什么 查看是哪条 .gitignore 规则忽略了该文件
git rm --cached 文件名 文件已经被 Git 跟踪,但现在希望忽略 停止跟踪文件,同时保留本地文件
git rm -r --cached 文件夹 一个已经被 Git 跟踪的目录现在需要忽略 停止跟踪整个目录,同时保留本地目录

需要特别记住:

.gitignore 不会自动停止跟踪已经 Commit 过的文件。

因此已经跟踪的文件,需要先使用:

git rm --cached 文件名

停止跟踪。


Tag 标签管理

指令 常用情况 作用
git tag 想查看项目有哪些版本标签 查看所有本地 Tag
git tag v1.0.0 当前 Commit 是一个重要版本 给当前 Commit 创建 Lightweight Tag
git tag -a v1.0.0 -m "Release version 1.0.0" 准备正式发布软件版本 创建带有作者、时间和说明的 Annotated Tag
git tag v1.0.0 CommitID 想给以前的某个 Commit 补 Tag 给指定历史 Commit 创建标签
git show v1.0.0 想查看某个版本对应的 Commit 查看 Tag 及其对应提交的详细信息
git tag -d v1.0.0 本地 Tag 创建错误 删除本地 Tag
git push origin v1.0.0 创建 Tag 后希望 GitHub 也拥有它 上传指定 Tag
git push origin --tags 本地存在多个 Tag,希望一起上传 把本地 Tag 推送到远程
git push origin --delete v1.0.0 远程 Tag 创建错误 删除 GitHub 中的指定 Tag
git switch --detach v1.0.0 希望查看或测试某个正式版本 临时切换到 Tag 对应的历史状态