本文简单的记录一下Git的一些基本的概念和基础的操作,主要是弄懂基本的东西,能够快速的上手并开始使用;但是更多深层的原理和应用还是需要另外去进一步学习的。

创建版本库

我们先整一个git仓库,再来看看基本概念;

新建版本库

  • 新建一个项目

默认打开的地址是应该是用户目录,也就是c盘Users下某个地方,下面就先在固定的地址新建一个空的目录作为我们的新项目,叫做FastApiProject

$ pwd # 查看刚进入的时候地址
/c/Users/minch

$ cd / # 返回根目录

$ cd /D/Coding/GitHouse/ # 切换到存放项目的地址

$ mkdir FastApiProject # 新建一个文件夹

$ cd ./FastApiProject/ # 切换到新建的目录下

  • 创建仓库

在项目目录下,输入git init命令初始化一个Git仓库;现在虽然创建好了,但是依旧是一个空的仓库,创建的时候也有提示;

$ git init
Initialized empty Git repository in D:/Coding/GitHouse/FastApiProject/.git/

image-20230629173600443

版本库基本概念

经过了上面的操作,再看下面的就简单多了;

版本库也就是git仓库,Git 仓库是用于版本控制的一个特殊目录(.git目录),它保存了项目的完整历史记录和元数据信息。Git 仓库存储了每个提交的快照,以及分支、标签、远程仓库等额外信息。Git 仓库是用于跟踪和管理项目中文件的更改的核心部分。

其实Git仓库就是一个文件夹,一个用来管理代码版本的文件夹。

Git 仓库对应一个存储库,它会记录每次对项目文件的修改。当您在 Git 仓库中进行更改时,Git 会跟踪这些变化并保存它们的历史记录。

这意味着,每当您在项目中添加、修改或删除文件时,Git 都会创建一个新的备份,称为提交(commit)。提交是代码修改的快照,并包含了作者、时间戳以及相关的元数据信息。

通过这些提交,Git 可以帮助您追踪项目历史,查看特定版本的代码状态,甚至回滚到之前的某个状态。

  • Git的元数据保存在.git文件夹里面

.git文件夹包含了记录代码历史和管理版本控制所需的所有信息。下面是.git文件夹中常见的一些重要文件和文件夹:

  • objects 文件夹:存储Git对象,其中包括提交(commit)、树(tree)和Blob对象(即文件内容)。
  • refs 文件夹:存储分支(branch)和标签(tag)引用的文件。例如,refs/heads 存储分支引用,refs/tags 存储标签引用。
  • config 文件:包含了Git 仓库的配置选项,例如用户名、邮箱等。
  • HEAD 文件:指向当前所在分支或提交的引用。
  • index 文件:暂存区(stage)的索引文件,记录即将提交的文件变更信息。
  • logs 文件夹:存储每次操作的日志信息,包括提交日志(commit logs)和引用日志(reflogs)。

.git文件夹中的这些文件和文件夹(以及其他一些附加文件)共同组成了Git版本库的结构,保存了项目的完整历史记录和相关元数据信息。通过读取和操作.git文件夹中的内容,Git可以进行版本控制、回溯历史、分支管理等操作。

现在其实就很好理解了,通常.git文件夹会被放置在项目目录的根目录下。

在项目目录中执行git init命令来初始化一个新的Git仓库时,Git会在当前目录创建.git文件夹,并将其作为Git仓库的根目录。这意味着该文件夹将包含Git仓库的所有信息和元数据。

所以正常应用也大可不必想上面那样去创建项目,直接在项目根目录下运行命令即可;

Git工作流程

  • 工作区(Working Directory): 工作区是实际进行代码编辑和文件修改的目录,也是开发过程中直接交互的地方。在工作区中,可以创建、编辑、删除文件,并对文件进行各种操作。这些操作仅在本地计算机上进行,不影响其他开发人员或远程仓库中的代码。通常来讲就是某个项目的根目录;

  • 本地暂存区(Staging Area): 本地暂存区是位于Git版本库内部的一个临时区域,用于暂存工作区中所做的修改。暂存区主要作用如下:

    1. 分离工作区和提交: 通过将工作区中的更改添加到暂存区,可以选择性地将一部分更改提交到本地仓库,而不是一次性提交所有更改。这样可以帮助进行更精细的代码管理和版本控制。
    2. 准备提交的更改: 暂存区可以帮助准备好要提交的更改。可以根据需要在工作区中进行多次修改,然后使用git add命令将所需更改添加到暂存区。添加到暂存区后,这些更改就准备好提交到本地仓库中。
    3. 查看更改内容: 使用git diff命令可以比较工作区和暂存区之间的差异,进一步清楚地了解即将提交的更改内容。这可以帮助检查更改是否符合预期,并在提交前进行必要的修改。
  • 本地仓库(Local Repository): 本地仓库是Git的核心组成部分,它保存代码仓库的完整历史记录。每次使用git commit命令将本地暂存区中的更改提交到本地仓库中时,Git会为该提交创建一个新的版本,并将其永久保存在本地仓库中,也就是上面提到的版本库。主要作用:

    1. 历史记录和版本控制: 本地版本库保存了代码仓库的完整历史记录。每当使用git commit命令提交更改时,Git会为该提交创建一个新的版本,并将其永久保存在本地版本库中。通过本地版本库,您可以追溯代码的演变历史,查看每个提交的详细信息,并轻松地进行版本控制。
    2. 回退和恢复: 本地版本库能够回退到先前的提交状态或恢复到特定的历史版本。通过使用git checkout命令,您可以切换到不同的分支、标签或具体的提交。这非常有用,当您需要回退错误的更改、测试旧版本的功能或处理紧急问题时。
    3. 与远程仓库的同步: 本地版本库可以与远程仓库进行同步,以便与团队共享代码和协作开发。通过使用git push命令将本地版本库中的更改推送到远程仓库,并使用git pull命令从远程仓库拉取最新的更改,可以与其他开发人员保持同步。
  • 远程仓库(Remote Repository): 远程仓库是位于云端的Git仓库,可以与团队成员共享代码。通过使用git push命令,您可以将本地仓库中的更改推送至远程仓库,以便与他人共享和协作。

  • add:将工作区中的更改添加到本地暂存区。

  • commit:将本地暂存区中的更改提交到地仓库,创建一个新的提交。

    主要完成的内容就是创建一个新的提交,包括暂存区中的所有更改;每个提交都有一个唯一的哈希值,用于在版本历史中标识该提交。提交时,可以提供一条有意义的提交消息来描述更改的内容。

  • checkout:用于在本地仓库中切换分支或恢复历史版本。

    主要操作是将Git版本库中的内容拿到工作区。例如回退版本,连续两天提交了版本,第三天的时候,想要将工作区的内容回退到第一天提交的版本,就需要checkout操作回退版本。

    或者从一个分支切换到另一个分支,分支的概念看下文;

  • clone:克隆远程仓库到本地,创建一个本地仓库的副本。

    克隆操作其实就是一个粘贴复制,把远程的仓库完整的拷贝到本地仓库;通常是包含两步:

    • 创建本地仓库:首先,在本地创建一个新的空白目录或指定已存在的目录作为本地仓库。这一步是为了给克隆的项目提供一个位置,用于存储远程仓库的内容和版本历史。
    • 克隆仓库:使用git clone命令,将远程仓库的内容复制到本地仓库中。克隆操作会自动将远程仓库的全部历史记录、分支信息和文件复制到新创建的本地仓库目录中,并为远程仓库设置一个别名(默认为“origin”)。
  • push:将本地仓库中的更改推送至远程仓库。

    将本地的提交推送到远程仓库,更新远程仓库的分支和提交历史。

  • pull:从远程仓库拉取最新更改(相当于fetch + merge)。

    其实也是两步;更新是从远程仓库(remote repository)到本地仓库(local repository),但实际的合并操作是将更改从本地仓库合并到工作区(working directory)和本地仓库的当前分支。

    • fetch:从远程仓库获取最新的提交、分支和标签信息,但不会自动合并到本地分支。
    • merge:将获取的最新提交合并到当前分支中,以保持与远程仓库同步。

image-20230629214712288

总结一下,git的流程涉及到四个位置,分别是工作区、暂存区、本地仓库、远程仓库;工作区就是项目目录,就是完整项目的根目录,暂存区和本地仓库都是git在本地工作涉及的两个位置,都位于项目目录下.git目录下;其次是远程仓库,远程仓库就是类似GitHub、gitee类的平台,其实就是互联网上的版本库;

完整的流程是新建一个项目,同时新建一个本地库,项目第一版部分代码开发完成后,提交代码到暂存区(add),等本次开发完成了,就将暂存区打代码提交到本地仓库(commit);发现有问题或者更新等需要切换版本的时候,就将本地仓库的内容回退到工作区(checkout);本地仓库完成提交后,就可以将仓库信息给推送到远程仓库存储起来,有修改之后,继续推送到远程仓库(push);另外的人想要接入项目,就从远程仓库克隆一下仓库,克隆到本地之后(clone),经过checkout的操作就可以在工作区看到对应版本的代码了;整个流程打通了之后,远程仓库发生修改了,就可以将远程的修改拉回本地(pull),实际也是拉回本地,进一步将版本切换到工作区;

大的流程如此,但是实际的使用过程还是相对复杂的,还涉及分支、合并等一系列的操作,但是理解这个基本流程后,后面的内容就都好理解了;

分支的基本概念

分支的创建和合并是版本控制系统中比较重要的操作,Git最初就是为了方便世界各地的Linux内核开发者设计的,就是要让分支的创建和合并变的简单而且安全。

工艺流程图

分支的概念是比较好理解的,git的版本库就是由很多个分支组成的,我们不创建新的分支的时候,默认就是main/master分支,也就是主分支,这个名称在安装的时候有提到过;

如果把每次commit看作一个版本提交,那么上面图片中的每个节点都可以看作一个版本,分支就是在项目的当前状态上创建了一个完全一样的“副本”,这个副本可以独立进行修改,而不影响其他分支或主分支。

在这个新的分支上,可以随意修改代码、添加新的功能、调试和测试,而不会对主分支上的代码产生任何影响。这个分支与主分支相互独立,可以将其看作是一个完整的项目副本。

当在这个分支上进行开发工作时,其他人可以继续在主分支上进行工作,互不干扰。这就是Git分支的优势之一:团队成员可以并行开发不同的功能,而不会影响彼此的工作。

当完成了在分支上的开发工作并测试通过后,可以将这个分支合并回主分支,以将新的功能或修复应用到整个项目中。Git提供了合并分支的功能,它会将分支上所做的更改整合到主分支上。

另外,Git还提供了切换分支的功能,可以在不同的分支之间自由切换。这意味着可以根据需要快速切换到不同的分支,查看或编辑特定的代码。

  • 工作目录和分支的关系

分支归根到底是git内的操作,工作目录是怎么样的呢?

当切换到一个新分支时,Git会根据该分支的最后一次提交更新工作目录。这意味着工作目录中的文件和目录会被替换为该分支的最新版本。如果在切换分支之前对工作目录进行了修改,那些修改可能会被保存下来,但在切换到新分支时,它们可能与新分支的代码产生冲突,需要进一步处理。

需要注意的是,未提交的修改不会随着分支的切换而消失。即使切换分支,那些修改仍然存在于工作目录中,只是这些修改可能与当前分支的代码出现冲突。在切换分支之前,可以使用git stash命令将这些修改暂存起来,以便稍后在相关分支上继续工作。

所以在本地操作的时候,切换分支的时候,工作目录中的内容也会切换;

标签的基本概念

标签就是给定版本的符号名称。它永远都指向相同对象,并且不会变更。标签的用途是,对于所有开发人员来说,都可以使用符号名称引用给定的修订,而且该符号对所有开发人员的意义都是一致的。

在Git中,标签(Tag)是用于给特定提交(commit)打上一个有意义的、永久性的标记。标签相当于一个固定指向某个特定提交的引用,通常用来表示项目的版本、发布或者重要的里程碑。

标签可以用来表示项目的版本号。当代码开发到一个稳定状态并准备发布时,我们可以给这个版本打上一个标签,方便其他人获取并确保他们拿到的是同一个版本的代码。

其次,标签还可以用来管理发布过程。每次发布新版本时,我们可以为这个版本创建一个标签。这样,我们可以方便地回溯、查看和获取这个特定版本的代码,并且同时也能追踪已发布版本的变化和修复。

另外,标签还可以用来标记项目开发过程中的重要里程碑,如测试阶段、功能完成、重要修复等。我们可以给这些重要节点打上标签,以后可以根据标签来查找相关的提交。

Gitcommit,为什么还要引入tag

“请把上周一的那个版本打包发布,commit号是6a5819e…

“一串乱七八糟的数字不好找!”

如果换一个办法:

“请把上周一的那个版本打包发布,版本号是v1.2

“好的,按照tag v1.2查找commit就行!”

所以,tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。

Git基本操作

提交文件到版本库

上面的项目是一个空的项目,里面一个文件都没有,所以先从零开始;

  • 在项目中新增一个readme文件

使用touch readme.md命令新增一个readme的MarkDown文件,这个项目就不是空的了;

  • 添加文件到暂存区

将要上传的文件添加到Git的暂存区,使用以下命令:

git add filename  // 添加单个文件
git add .         // 添加所有文件(包括新的和修改过的)

例如将我们刚才创建的readme.md上传到暂存区,没有报错就是上传成功了;

image-20230629215928747

我们再新建两个文件,然后上传所有文件,如下,没有报错就成功了;

image-20230629220133850

  • 提交代码到Git仓库

将暂存区中的更改提交到代码库,使用以下命令:

git commit -m "Commit message"  // 提交并添加提交信息

-m后面输入的是本次提交的说明,一般用来记录改动记录,这个说明是非常必要的,个人的项目方便回退查看,团队项目方便阅读;

上传结果如下,提交成功后,git会有提示,在这次提交中,共有3个文件被更改,但没有插入或删除任何内容。

image-20230629220333325

常用查看版本库的命令

  1. 查看提交历史:使用git log命令可以查看提交历史,包括每个提交的哈希值、作者、提交日期和提交消息等信息。默认以最新的提交开始显示,按照时间倒序排列。

    git log
    
  2. 查看文件变更:使用git diff命令可以比较当前工作目录中的文件与最新提交之间的差异。它可以显示插入的内容、删除的内容以及修改的内容等信息。

    git diff
    
  3. 查看文件状态:使用git status命令可以查看工作目录中文件的状态,包括已修改、已暂存、未跟踪等状态。它会列出所有变更的文件以及它们所处的状态。

    git status
    
  4. 查看特定提交的内容:使用git show命令可以查看某个特定提交的详细信息,包括提交的更改内容和元数据。需要提供该提交的哈希值或其他引用(如分支名)。

    git show commit_hash
    

以上是一些常用的Git命令,用于查看版本库中的内容。通过这些命令,您可以了解提交历史、文件变更以及当前文件的状态,进而进行版本控制和代码调试等操作。

更新版本并提交

上面的操作,相当于是完成了第一版的提交,接着进行文档修改后上传,查看相关的一些变化。

上面初始的版本中,包含三个文件,分别是readme.md、test.py、test2.py;

test.py中写入print("Hello world!")

test2.py中写入print("Hello test2!")

这里写入的方式有很多,建议是使用安装的时候的vim就行,也可以通过其他编辑器编辑也行,这无所谓;

  • 通过git diff查看工作区的文件变更

如下图,运行命令后输出了相关提示;

在输出中的警告表示,在下次Git操作时,LF(Line Feed,换行符)将被CRLF(Carriage Return Line Feed,回车换行符)所取代。这通常发生在Windows环境下,因为Git在Windows上默认使用CRLF作为换行符,而不是Unix风格的LF。这个警告是提醒你关于换行符的差异,但不会影响实际的差异显示和文件修改。

接下来是具体的差异内容,使用---表示原有文件的位置,+++表示修改后的文件的位置。在每个文件的差异后面,使用@@ -x,y +z,w @@格式的行表示差异的位置信息。其中,x,y表示原有文件中被修改部分的起始行和结束行,z,w表示修改后的文件中对应的起始行和结束行。

也提示test.py文件添加了一行代码print("Hello world!"),而test2.py文件也添加了一行代码print("Hello test2!")

image-20230630202201485

  • 上传test2.py到暂存区

image-20230630203232466

  • 使用git status查看文件状态

逐行解释:

在main分支中

Changes to be committed:这一部分列出了即将被提交的修改。在这里,test2.py文件被修改并已经添加到了暂存区。

可以使用git restore --staged <file>...命令来取消对文件的暂存操作,将其移出暂存区。

modified指示被修改还未提交的文件;

Changes not staged for commit:这一部分列出了未暂存的修改。在这里,test.py文件被修改但没有被添加到暂存区。

可以使用git add <file>...命令将文件添加到暂存区,以将其包含在下一次的提交中。

image-20230630203322295

再修改一下readme.md,并查看多个文件的时候的状态;

修改未暂存:

image-20230630204058223

修改并暂存:

image-20230630204133843

  • 提交到Git库中

image-20230630210742108

  • 使用git log查看版本库内的上传日志

可以看到提交了两次,以及每次提交的时候的基本信息;

image-20230630210931876

  • 通过git show查看第二次上传的详细信息

就可以看到本次上传的主要信息了;

命令版本号没必要写全,前几位就可以了,Git会自动去找。当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。

image-20230630211230983

  • 修改test2.py并上传所有文件

这里就可以看到第三次提交的信息,首先是test.py新增了一行代码,其次是test2.py中代码发生了变化;

image-20230630212142222

以上就是git版本更新的基本操作和信息查看;

版本回退

reset/checkout的区别

为什么resetcheckout要单独拿出来说,是因为版本回退在git中涉及版本回退有两个常见的操作,当涉及到回退版本或切换分支时,git resetgit checkout是两个常用的Git命令,并且有一些区别。

  1. git reset命令:

    • 作用:git reset用于移动HEAD指针和当前分支引用来更改提交历史。

    • 提交历史:git reset会修改提交历史,因此在公共仓库中使用时需要小心。它可以撤销提交、删除提交或重写提交历史。

    • 索引和工作目录:git reset根据指定的参数选项(如--mixed--soft--hard)来决定是否更改索引和工作目录。

      • --soft:仅移动HEAD指针和当前分支引用,不更改索引和工作目录。这允许你撤销最近的提交并重新提交。
      • --mixed(默认选项):移动HEAD指针和当前分支引用,并将索引重置为指定的提交。但是,不更改工作目录。这样可以撤销提交并保留更改的副本供进一步修改。
      • --hard:彻底移动HEAD指针、当前分支引用和索引,并重置工作目录为指定的提交。这将丢弃所有未提交的更改。
  2. git checkout命令:

    • 作用:git checkout用于切换分支、还原文件或查看历史版本。
    • 提交历史:git checkout不会更改提交历史。它主要用于浏览和查看已经存在的提交。
    • 分支和文件:git checkout可以通过指定分支或提交标识符,切换到不同的分支或恢复特定版本的文件。它会将HEAD指针和当前分支引用移动到新的目标。

简而言之,git reset主要用于修改提交历史,并具有对索引和工作目录的不同影响。而git checkout主要用于切换分支、还原文件和查看历史版本,不会修改提交历史。

git checkout

我们先看看checkout的版本回退操作,至于切换分支等操作,后面再讲,这里只将回退;

为了理解,我们重新创建一个项目,只有一个test.py文件;

第一版

第二版

print("2.0")

第三版

print("3.0")

image-20230702132319235

  • 开始回退

现在我们觉得第三次提交的内容中test.py修改的内容不对,我们要回到第二版,在第二版的基础上调整;使用git checkout <commit_id>就可以实现回退了

image-20230702132516119

如上,就已经完成的切换,Git发出提示:

切换回去之后,就开始没有关联任何分支了,相当于是把那个版本拿出来独立在分支之外了;

也就是说,checkout会切换到旧版本,切换回去之后可以查看旧版本的状态,但是他并不能改变提交历史,也就是不管你怎么操作,都不会改变当前分支的提交记录和版本;

如果要保留checkout之后的修改,可以创建一个新的分支;

  • 查看一下这个时候test2.py的内容,可以发现,已经切换成功了

image-20230702124708614

  • 接着在第二版的基础上进行修改:
print("2.0——>4.0")
  • 进行一下提交,然后查看log

image-20230702154850384

发现已经成功了,但是是在第二版上叠加了一个版本,并不是在第三版的基础上叠加了版本,这就印证了前面说的不会修改分支的提交历史;

  • 再切换到主分支查看一下

image-20230702154706084

这个时候有个报错,说切换回main分支的时候,有一个提交不属于任何分支,可以选择创建一个新的分支来保留这个提交。然后可以切换到新的分支上进行开发或修改。

  • 看看main分支的log

image-20230702155409735

这里就可以看到,main分支的提交历史并没有发生任何变化;

那么如何将那个孤立的提交给放到main分支里面做第四版呢?

其实是不能够直接做到的,那你会问这样的checkout有什么意义,当然有,只是流程不能是切换到旧版本,然后修改提交,然后将孤立的那个提交直接拿到旧分支中;两个方案:

  • 首先就是按照git的提示那样,创建一个新的分支,然后将新分支合并到旧分支中(具体操作在后面的分支去记录);
  • 其次是我们checkout回旧版本后,修改了不要提交,而是将修改暂存,然后切换回旧分支,拉回修改进行合并;
  • 演示第二个合并的方案

首先切换到第二版本的分支,然后修改文件,注意这里是重新回到第二版,然后重新修改代码

上面的修改和提交依旧还存在;

也就是我们最开始切换到第二个版本,修改代码提交的那个‘第四版’;现在不属于任何分支,也称作游离提交;

游离提交无法通过常规的 Git 命令进行删除,提交历史是 Git 存储的一部分,游离提交会在一段时间后被 Git 的垃圾回收机制清理掉。

接着通过git stash save "Your stash message"保存修改到临时区:

image-20230702164120331

切换回主分支:

image-20230702164136155

查看暂存区内容:

image-20230702164830620

将暂存区的内容应用到当前分支:

image-20230702164850595

这里就开始提示在合并时遇到冲突,由于两个地方都有修改,但是git不知道保留哪个,所以报错,可以看到现在文件里面的内容如下:

image-20230702165837341

需要自行进行编辑选择;这里我们不选择,直接上传,提交;

image-20230702165134414

如上,我们就完成了一个版本的切换;

但是我们发现,这个暂存还在:

image-20230702165340659

可以通过git stash drop <stash_id>删除:

image-20230702165623429

这样就完成了删除,或者将上面的git stash apply换成git stash pop;git会在应用暂存的时候同时删除那个暂存;

git reset

上面,我们的版本库中已经有四个版本了;可以通过git log可以直接查看(这里博主换了个环境重新搭的,所以hash值和上文不一样,注意区分噢~):

image-20230728171307393

工艺流程图

用于回退 Git 提交的通常包含三个命令,它们之间的区别在于对暂存区和工作目录的处理方式不同。

  1. git reset --soft:
    • 这个命令会将当前分支的 HEAD 指针指向指定的提交,同时保留之前的修改内容和暂存区的文件
    • 它不会改变工作目录的文件状态,也不会删除已提交的历史记录。
    • 通过这个命令,你可以撤销之前的提交,将其作为未提交的修改保留下来,方便进行新的提交。
  2. git reset --mixed:
    • 这个命令的行为与默认的 git reset 命令相同。
    • 这个命令会将当前分支的 HEAD 指针指向指定的提交,同时将之前的修改内容放入工作目录,并取消暂存区的文件
    • 它会保留之前的修改作为未暂存的修改,需要重新添加和提交文件。
  3. git reset --hard:
    • 这个命令会彻底丢弃当前分支的 HEAD 指向的提交以及之后的所有提交。
    • 它会将当前分支的 HEAD 指针指向指定的提交,并将之前的修改内容从工作目录、暂存区和 Git 历史记录中全部移除
    • 执行这个命令后,之前的修改将无法恢复。
    • 注意:在使用这个命令时,请谨慎操作,以免意外丢失重要的修改。

总结:

  • git reset --soft:保留修改和暂存区的文件,可重新提交。
  • git reset --mixed:保留修改但取消暂存,需要重新添加和提交文件。
  • git reset --hard:彻底丢弃当前提交及之后的修改,无法恢复。
  • 准备工作

为了区分工作区、暂存区、git提交历史;首先修改一下最新的文件,然后添加到暂存区,接着再修改一下文件;

在这里,本来是print("2.0-->4.0");

暂存区的内容是print("3.0-->4.0")

image-20230728171423388

工作区的内容是print("4.0")

image-20230728171455609

  • git reset --soft

使用git reset回退,上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100;

当然也可以使用提交的哈希值来定位某次提交;

image-20230728171655930

运行上面命令,就可以完成回退了,查看提交日志,发现第四次提交已经不再了;

image-20230728171705690

然后分别查看暂存区和工作目录的文件内容;

暂存区如下,可以发现和回退前一致:

image-20230728171849828

工作目录的内容也是和回退前一致的操作;

image-20230728171923797

后悔了怎么办

后悔了也就太简单了,还是使用git reset --soft

但是不能用HEAD了,要找到对应的那个提交的哈希值,在这个案例中就是第四次提交的哈希值:

image-20230728174311349

查看提交的版本中内容、暂存区内容、工作区内容:

版本中和还原前一致

image-20230728174348110

暂存区和还原前一致:

image-20230728174415990

工作区和还原前一致:

image-20230728174446776

  • git reset --mixed

同样使用HEAD^的方式回退,回退成功后给出提示:

表示 test.py 文件有未暂存的更改。由于使用了 --mixed 参数,保留工作区修改但取消暂存。

image-20230728175116504

查看本版本内容,是对应第三版的内容:

image-20230728175336523

暂存区的内容空了:

image-20230728175306190

工作区的内容没变:

image-20230728174740140

这里后悔了的话,是不能完全回去的哈,就是暂存区的内容已经删除了,再回去的话,暂存区的内容也没了;

(如果有办法的话,欢迎纠正哈)

  • git reset --hard

在此之前,我们还是将工作区、暂存区的内容调整到最初的一样哈;

image-20230730171140275

然后使用使用HEAD^的方式回退,命令:git reset --hard HEAD^

查看版本中内容,内容回到了第三版的内容:

image-20230730171449666

暂存区内容为空:

image-20230730171457435

工作区内容为空:

image-20230730171521834

这里后悔了,要回到第四版的话,也是很简单的,直接使用命令:git reset --hard a5b4即可;

同样,回到第四版,也不能恢复暂存区和工作区的内容。

git reset拓展

不仅仅只有上述三种形式的 git reset 命令,还有其他一些变种形式。

除了 git reset --soft, git reset --mixedgit reset --hard,还有其他的 git reset 变种命令:

  1. git reset HEAD <file>:

    • 这个命令用于取消已经暂存的文件,将文件从暂存区移回到工作目录。
    • <file> 参数代表要取消暂存的文件名。

    如下,可以将指定的文件从暂存区恢复到工作区。

    image-20230730180725398

  2. git reset <commit>:

    • 这个命令用于将当前分支的 HEAD 指针指向指定的提交。
    • <commit> 可以是提交的哈希值、分支名或标签名。

    这个其实和git reset --mixed是一样的;

  3. git reset --keep <commit>:

    • 这个命令用于移动当前分支的 HEAD 指针到指定的提交,并保持工作目录的状态不变。
    • 它会尝试应用之前提交的更改,如果存在冲突,则命令会终止并保留冲突文件供解决。
    1. 将当前分支的指针(HEAD)和索引(暂存区)移动到指定的 <commit>,这样就撤销了 <commit> 以及之后的所有提交。
    2. 不像其他的 reset 模式,--keep 选项会保留工作目录中的所有修改。这意味着未添加到索引的更改不会丢失。
    3. 如果工作目录存在与 <commit> 不一致的部分,那么这些更改将会被保留,但会被标记为未暂存的更改。

分支的使用

使用分支的好处是可以保持代码库的整洁同时允许并行开发。每个人可以在自己的分支上工作,不会影响到其他人。当一个功能或修复完成后,可以将分支合并回主分支(通常是 master 分支),从而将更改整合到项目中。

默认分支:master

在 Git 中,默认创建的分支通常被称为 mastermain 分支。这是代码库的主要分支,包含了最新可用的稳定代码。

创建新分支

要创建新的分支,可以使用以下命令:

git branch <branch_name>

这将在当前提交上创建一个名为 <branch_name> 的新分支,但还没有切换到该分支。

如下,没有报错即新建成功!

image-20230801172158836

切换分支

要切换到已存在的分支,可以使用以下命令:

git checkout <branch_name>

这将会将工作目录和代码库切换到名为 <branch_name> 的分支上。

image-20230801172135789

创建并切换分支

git checkout命令加上-b参数表示创建并切换:

image-20230801173254086

Git 2.23 版本之后,可以使用以下命令来快速创建并切换到新分支:

git switch -c <branch_name>

这将创建一个名为 <branch_name> 的新分支,并将工作目录和代码库切换到该分支上。

image-20230801172556689

查看分支

要查看所有本地分支,可以运行以下命令:

git branch

当前分支前面会有一个星号 *。另外,可以添加 -r 选项来查看远程仓库的分支。

image-20230801172912690

提交版本

修改一下文件内容,将里面的内容修改为5.0并提交,都是同样的操作:

image-20230801173506032

合并分支

当在一个分支上工作完成后,通常需要将其合并回主分支或其他目标分支。要合并分支,可以使用以下命令:

git merge <branch_name>

这将把名为 <branch_name> 的分支合并到当前所在的分支(通常是 master 分支)。

如下,先切换回主分支,然后将分支branch1合并到当前分支,然后查看提交历史:

image-20230801173959271

删除分支

当分支的任务完成后,可以删除不再需要的分支。要删除分支,可以使用以下命令:

git branch -d <branch_name>

这将删除名为 <branch_name> 的分支。如果分支上还有未合并的更改,需要使用 -D 参数来强制删除。

switch命令

Git 2.23 版本引入了 git switch 命令,用于切换分支和创建新分支。git switch 命令的功能与之前的 git checkout 命令有所重叠,但是更加直观和易于使用。

1. 切换到已存在的分支

要切换到已存在的分支,可以使用以下命令:

git switch <branch_name>

这将使当前工作目录切换到名为 <branch_name> 的分支。

2. 创建并切换到新分支

要创建一个新分支并立即切换到该分支,可以使用以下命令:

git switch -c <new_branch_name>

这将创建一个名为 <new_branch_name> 的新分支,并将当前工作目录切换到该分支。

3. 切换到远程分支

对于一个远程分支,你可能需要先将其拉取到本地,然后再切换到该分支。可以使用以下命令完成这个过程:

git switch --track <remote_branch_name>

这将拉取名为 <remote_branch_name> 的远程分支并创建一个与其关联的本地分支,并将当前工作目录切换到该分支。

4. 强制切换分支

如果在切换分支时存在未提交的更改,Git 默认情况下会阻止你切换分支。然而,有时你可能希望强制切换分支并放弃未提交的更改。可以使用以下命令:

git switch -f <branch_name>

这将强制将当前工作目录切换到名为 <branch_name> 的分支,并丢弃未提交的更改。

switch 和 checkout对比

git switchgit checkout 是在 Git 中用于切换分支的两个命令,它们有一些区别。以下是它们之间的主要区别:

  1. 操作方式不同: git switch 的操作方式更加直观和一致。它专门用于切换分支和创建新分支,更符合用户的直觉。而 git checkout 则具有更多的功能,可以用于切换分支、创建新分支、恢复文件等。
  2. 引起修改的情况不同: 在某些情况下,使用 git checkout 可能会导致未提交的更改被覆盖或丢失。例如,在切换分支之前,如果有对当前分支已修改但尚未提交的文件进行更改,那么 git checkout 会直接将这些更改应用到目标分支。这可能会导致不可预料的结果。相比之下,git switch 不会自动应用未提交的更改,它会提醒你先处理这些更改,然后再切换分支。
  3. 语义化的分支操作: git switch 的命令参数和选项更加语义化和直观。例如,使用 -c 选项来创建并切换到一个新分支,使用 --detach 选项来切换到一个游离的 HEAD(不指向任何分支)。这使得分支操作更加易于理解和记忆。
  4. 后续开发和推荐性: git switch 是在 Git 2.23 版本中引入的新命令,而 git checkout 是 Git 的旧命令。随着时间的推移,Git 社区更倾向于使用和推荐 git switch 命令,因为它更直观、功能单一,并且在处理未提交的更改时更加安全。

需要注意的是,虽然 git switch 更加推荐使用,但在早期版本的 Git 中仍然可以使用 git checkout 命令,并且它仍然是有效的。对于一些高级或特殊的用例,git checkout 可能提供更多灵活性和功能。

总的来说,git switch 是一个更简洁、直观并且推荐使用的命令,专门用于分支切换和创建新分支。而 git checkout 则是一个更通用、功能更多的命令,可以用于更多其他场景,如恢复文件、创建或删除分支等。

标签的使用

添加标签

切换到对应的分支,使用命令:git tag tagname为最新的提交打标签:

image-20230801180555121

使用命令git tag查看所有标签:

image-20230801180604450

为历史提交打标签

之前某次提交忘记打标签了,为其打标签:

首先查看历史提交,git log --pretty=oneline --abbrev-commit,该命令的作用是以单行的形式显示提交历史,并使用缩略的提交哈希值。

image-20230801180901241

为第一版打标签:

image-20230801180947942

git show <tagname>查看标签信息:

image-20230801181012342

删除标签

使用git tag -d tagname即可完成标签的删除;

image-20230801181125268

远程仓库

远程仓库有很多,流行的主要就有github、gitee等,这里使用gitee来演示,创建账号和新建仓库具体操作就不演示了;

添加到远程仓库

  • 首先新建一个远程仓库

这里新建一个test_gitee的远程仓库;

image-20230730185232252

  • 使用 git remote add 命令,并将 <remote_name> 替换为想要的远程仓库名称,<remote_url> 替换为远程仓库的 URL。使用git remote -v查看远程仓库详细信息:

    image-20230730190416516

    这里是可以添加多个远程仓库的噢

  • 推送到远程仓库

    使用 git push 命令将本地仓库中的分支推送到远程仓库。指定要推送的分支,以及远程仓库的名称和分支。例如,以下命令将本地 main 分支推送到名为 origin 的远程仓库。Copy Codegit push <remote_name> <branch_name>

    如果是首次推送分支,可能需要使用 -u 参数来设置跟踪。例如:

    Copy Codegit push -u <remote_name> <branch_name>

    如下,便完成了推送:

    image-20230730194523198

    • Enumerating objects: 12, done.:Git 正在遍历要推送的对象。
    • Counting objects: 100% (12/12), done.:Git 统计了要推送的对象数量。
    • Delta compression using up to 16 threads:Git 使用了多线程进行增量压缩。
    • Compressing objects: 100% (4/4), done.:Git 完成了对象的压缩。
    • Writing objects: 100% (12/12), 947 bytes | 947.00 KiB/s, done.:Git 将对象写入远程仓库。
    • Total 12 (delta 0), reused 0 (delta 0), pack-reused 0:Git 显示了推送的总体情况,此次推送没有增量数据(delta)。
    • remote: Powered by GITEE.COM [GNK-6.4]:远程仓库的信息,显示你正在使用 Gitee 平台。
    • To https://gitee.com/zishu-yanluo/test_gitee.git:推送的目标远程仓库 URL。
    • [new branch] master -> master:远程仓库中创建了一个新分支 master,与本地的 master 分支关联。
    • branch 'master' set up to track 'test_gitee/master'.:本地的 master 分支已配置跟踪远程仓库的 test_gitee/master 分支。

    在远程仓库中也可以查看到我们的提交了:

    image-20230730194717690

拉取远程仓库

从远程仓库中获取最新的代码更新是很重要的,就像从云盘上下载最新的文件到你的电脑一样。要拉取远程仓库的更新,需要执git pull操作:

git pull 命令的一般语法为:

git pull <远程仓库名> <远程分支名>

具体解释如下:

  • <远程仓库名>:指定要获取更新的远程仓库,通常是使用 origin 来表示默认远程仓库。
  • <远程分支名>:指定要获取更新的远程分支。

git pull 命令的执行过程大致如下:

  1. 首先,它会自动调用 git fetch 命令,从指定的远程仓库中获取最新的提交,但不会应用到本地分支。
  2. 然后,它会自动调用 git merge 命令,将获取的提交与当前分支进行合并。

在执行 git pull 命令时,可能会遇到以下情况:

  • 如果本地没有未提交的修改,git pull 会自动合并远程分支的更新到当前分支,并创建一个新的合并提交。
  • 如果本地有未提交的修改,git pull 默认会尝试自动合并。如果合并过程中发生冲突,你需要手动解决冲突后再提交。
  • 如果你想要强制执行 git pull,可以使用 git pull --force 命令。

另外,还有一些 git pull 命令的选项可以进一步控制其行为,例如:

  • --rebase:使用 rebase 而不是 merge 来合并远程分支的更新。
  • --no-commit:获取远程更新后不自动创建新的合并提交。
  • --ff-only:仅在快进合并的情况下才执行合并操作,否则终止。

如下,现在远程仓库的版本是第四次提交

现在新建一个分支并回退到第三版:

image-20230802182027080

运行git pull命令没报错即拉取成功:

image-20230802182155328

克隆远程仓库

在使用 git clone 命令进行克隆时,你有两种选择:

  1. 克隆到新建的项目目录:你可以指定一个新的项目目录,在该目录下执行 git clone 命令来克隆远程仓库。这将在指定的目录中创建一个新的项目,并将远程仓库的内容复制到该目录中。

    例如:

    git clone <远程仓库地址> <新项目目录>
    

    在这种情况下,git clone 命令会自动创建一个与远程仓库同名的项目目录,并将远程仓库的内容复制到该目录中。

  2. 克隆到已存在的项目目录:如果你想将远程仓库的内容复制到一个已存在的项目目录中,可以直接进入该目录,并执行 git clone 命令。这将在当前目录中创建一个新的分支,并将远程仓库的内容复制到该分支中。

    例如:

    cd <已存在的项目目录>
    git clone <远程仓库地址>
    

    在这种情况下,git clone 命令会将远程仓库的内容复制到当前目录中,并自动创建一个新的默认分支。

  • 这里以很火的java开源博客系统halo为例:

image-20230802183333296

注意:

默认情况下,git clone 命令会克隆远程仓库的所有分支。但是,克隆下来的分支在本地仓库中会以远程分支的形式存在,并不会自动创建与每个远程分支对应的本地分支。

你可以使用 git branch -r 命令查看克隆下来的所有远程分支,使用 git branch -a 命令查看所有本地分支和远程分支。

image-20230802183513370

要将远程分支创建为本地分支,可以使用以下命令:

git checkout -b <本地分支名> <远程仓库名/远程分支名>

这将创建一个新的本地分支,并将其设置为指定远程分支的跟踪分支。

另外,如果你只想克隆特定的分支而不是所有分支,可以使用 --single-branch 选项。例如:

git clone --single-branch -b <分支名> <远程仓库地址>

这样只会克隆指定的分支,并忽略其他分支。

远程分支与标签操作

  • 分支

    1. 创建远程分支并推送:要在本地创建一个新分支,并将其推送到远程仓库,可以使用以下命令:

      git checkout -b <branch-name>
      git push origin <branch-name>
      

      这将创建一个名为<branch-name>的新分支,并将其推送到名为origin的远程仓库。

    2. 删除远程分支:要删除远程仓库中的分支,可以使用以下命令:

      git push origin --delete <branch-name>
      

      这将从远程仓库中删除名为<branch-name>的分支。请确保你有足够的权限来执行该操作。

    3. 查看远程分支:要查看远程仓库中的分支,可以使用以下命令:

      git branch -r
      

      这将显示远程仓库中的所有分支。

    4. 拉取远程分支:要将远程仓库的特定分支拉取到本地仓库,可以使用以下命令:

      git checkout -t origin/<branch-name>
      

      这将创建一个与远程仓库中的<branch-name>分支相对应的本地分支,并将其切换到该分支。

  • 标签

创建的标签都只存储在本地,不会自动推送到远程。

需要使用git push origin <tag-name>命令显式地将标签推送到远程仓库。

或者推送全部标签:git push origin --tags

可以使用git tag -d <tag-name>命令删除本地的标签。类似地,通过git push origin :refs/tags/<tag-name>命令可以从远程仓库删除标签。

具体用法如下:

  1. <tag-name>表示你要删除的标签的名称。
  2. origin是远程仓库的名称,通常指向你的远程仓库URL。
  3. 在命令中使用冒号(:)来指示删除操作,冒号前为空,表示引用的空值。

举个例子,如果你想删除名为v1.0的标签,可以执行以下命令:

Copy Codegit push origin :refs/tags/v1.0

执行命令后,Git会将该命令推送到远程仓库,远程仓库会删除对应的标签。

需要注意的是,这个命令只会删除远程仓库中的标签,而不会影响本地仓库中的标签。

Q&A

两本地仓库有一个同样的分支,同时推送到远程仓库会怎么样?

如果两个人的本地仓库都有一个同样的分支,并且同时推送到远程仓库,会导致冲突的发生。这是因为远程仓库不能直接处理两个相互冲突的提交。

具体情况如下:

  1. 假设两个人(Person A和Person B)都从远程仓库克隆了一个相同的分支,并在各自的本地仓库中进行了修改。
  2. Person A 先完成了修改并将其推送到远程仓库。
  3. 接下来,Person B 也希望将自己的修改推送到远程仓库。然而,由于此时远程仓库已经包含了 Person A 的提交,Person B 的推送会被拒绝,并且提示存在冲突。

在这种情况下,解决冲突的方法如下:

  1. Person B 需要先拉取最新的远程更新到本地仓库,使用 git pull 命令。
  2. git pull 命令会合并远程分支的更改到本地分支,并且可能触发冲突。
  3. 如果发生冲突,Person B 需要手动解决冲突。打开包含冲突的文件,根据标记手动编辑文件,解决冲突并保留需要的更改。
  4. 解决冲突后,使用 git add 命令将修改的文件标记为已解决冲突。
  5. 最后,使用 git commit 命令提交解决冲突后的更改。此时,会生成一个新的合并提交。
  6. 接下来,Person B 可以再次尝试推送自己的提交到远程仓库。

总之,如果两个人的本地仓库都有相同的分支,并且同时推送到远程仓库,会导致冲突的发生。在这种情况下,需要先拉取最新的远程更新,解决冲突后再推送修改到远程仓库。这样可以确保所有人的更改都能够合并,并保持代码的一致性。


欢 迎 关 注 扫 码 关 注!