type
status
date
slug
summary
tags
category
icon
password
URL-TEXT
😀
本教程旨在帮助你成为Git的熟练用户,而非深入钻研的专家。虽然Git有很多高级命令,可能只有真正的专家才能完全理解,但本教程将专注于那些对你日常工作至关重要的命令。你无需花费时间去学习那些几乎永远不会用到的“高级”命令。一旦你遇到确实需要这些高级命令的情况,再通过网络搜索或向专家求助也不迟。
作为一名开发人员,如果你想掌握目前最先进的分布式版本控制系统,那么现在就开始学习吧!
 

一、什么是Git

Git是目前世界上最先进的分布式版本控制系统(没有之一)。
版本控制系统(Version Control System,VCS)是一种软件工具,用于追踪代码、文档或其他文件在多个版本之间的变化。它可以帮助用户管理文件的修改历史,允许用户比较不同版本之间的差异,回滚到以前的版本,甚至合并来自不同用户的修改。
以Microsoft Word为例,如果你在没有版本控制的情况下编辑一个长文档,你可能会采取“另存为”的方式来保存不同阶段的修改。这样做虽然可以保留一些历史版本,但管理起来非常不方便。你可能会很快遇到文件名混乱、文件占用空间巨大、难以追踪具体修改等问题。
notion image
一旦你急需找回被删除的文字,但记忆的模糊让你无法确定是在哪个文件中删除的。你不得不逐个打开文件,逐一检查,这无疑是一项繁琐且耗时的任务。
眼前堆积如山的文件让你陷入困境。你希望保留最新的版本,但又担心未来可能需要使用到旧的版本,因此不敢随意删除。这种进退两难的境地让人倍感郁闷。
更糟糕的是,当你需要与同事合作填写部分内容时,收到修改后的文件,你需要仔细回忆自发送文件以来你所做的所有更改,以便将它们与同事的更改合并。这无疑增加了合作的复杂性和困难。
在这一刻,你渴望有一个软件能够自动记录每次文件的变动,并允许团队成员协作编辑。这样,你就不必再手动管理一堆相似的文件,也不必再担心文件的传输和合并问题。如果你想要查看某个特定版本的更改,只需在软件中轻松查看即可。
想象一下,这样的软件能够记录每次文件的变动,并清晰地展示给用户:
版本
文件名
用户
说明
日期
1
project.txt
Alice
初始创建项目文件
8/1 9:00
2
project.txt
Bob
添加了项目需求文档
8/2 11:30
3
project.txt
Alice
修改了项目时间表
8/3 14:15
4
project.txt
Charlie
添加了设计草图
8/4 17:45
5
project.txt
Bob
更新了项目预算
8/5 9:30
通过使用这样的软件,你将能够告别手动管理多个版本的繁琐时代,迈进高效便捷的版本控制新时代。这样,你就可以轻松地追踪和管理文件的变动,与团队成员协作更加顺畅,大大提高了工作效率。

1.1 Git的诞生

众所周知,Linus Torvalds在1991年创建了开源的Linux操作系统,自此,Linux系统不断发展壮大,逐渐成为了最大的服务器系统软件。尽管Linus是Linux的创始人,但Linux的成功却归功于全球各地的热心志愿者们的共同努力,他们为Linux编写代码,不断推动着系统的发展。
然而,在2002年之前,Linux的代码管理却是一项繁琐而复杂的任务。志愿者们将源代码文件通过diff的方式发送给Linus,然后由Linus本人手工合并代码。这种方式既效率低下,又容易出错,显然无法满足Linux快速发展的需求。
你可能会好奇,为什么Linus没有选择使用像CVS或SVN这样的版本控制系统来管理Linux的代码呢?原因在于Linus坚决反对这些集中式的版本控制系统,他认为这些系统速度慢,且必须联网才能使用。尽管市面上有一些商用的版本控制系统比CVS和SVN更先进,但它们都是付费的,这与Linux的开源精神相悖。
到了2002年,Linux系统的代码库已经变得非常庞大,Linus很难再继续通过手工方式管理。同时,社区的开发者们也对这种方式表达了强烈的不满。在这种情况下,Linus不得不做出改变。他选择了一个商业的版本控制系统BitKeeper。BitKeeper的开发者BitMover公司出于人道主义精神,授权Linux社区免费使用这个版本控制系统。
这种平静的局面并没有持续太久。2005年,由于Linux社区中的一些开发者试图破解BitKeeper的协议,被BitMover公司发现后,BitMover公司决定收回Linux社区的免费使用权。面对这种情况,Linus并没有选择出面协商,而是决定自己开发一个新的版本控制系统。
在短短的两周时间内,Linus用C语言编写了一个分布式版本控制系统,没错,它就是Git!一个月内,Linux系统的源码已经完全由Git管理。这个决定不仅解决了Linux社区面临的困境,也让Git迅速成为了最流行的分布式版本控制系统。
2008年,GitHub网站上线,为开源项目免费提供Git存储。这一举措吸引了无数开源项目迁移到GitHub,包括jQuery、PHP、Ruby等等。可以说,如果不是当年BitMover公司的威胁,我们可能无法拥有如今这样免费且超级好用的Git。

1.2 集中式vs分布式

上节说到,Linus一直对CVS和SVN持有不满,因为它们都属于集中式的版本控制系统。相比之下,Git是一个分布式版本控制系统。这两者之间的核心区别在于数据的存储和协作方式。
在集中式版本控制系统中,如CVS和SVN,所有的版本信息都存储在中央服务器上。用户在工作时,需要先从服务器获取最新版本的文件,然后在本地进行修改。完成修改后,再将改动推送回中央服务器。这种模式类似于图书馆借书:你想修改一本书,必须先从图书馆借出,修改完成后再归还。这种方式的最大限制是,它必须依赖网络连接。如果网络连接不稳定或速度较慢,例如在互联网上提交大文件,可能会耗费大量时间。
而分布式版本控制系统如Git则完全不同。每个人的电脑上都保存有一个完整的版本库,这意味着你可以在没有网络连接的情况下进行工作。当多人协作时,例如你和同事都修改了同一个文件,你们只需将各自的修改推送给对方,就能相互看到对方的改动。这种设计大大提高了版本控制系统的灵活性和安全性。即使某人的电脑出现故障,也可以从其他人的电脑上复制完整的版本库。而且,由于每个人的电脑上都有完整的版本信息,所以即使中央服务器出现问题,大家仍然可以继续工作。
notion image
在实际应用中,分布式版本控制系统也会设立一个中央服务器,但它主要用于方便团队成员交换修改。即使没有这个服务器,大家仍然可以正常工作,只是交换修改会稍显不便。
Git的优势不仅在于无需联网,其强大的分支管理功能也是其他版本控制系统难以比拟的。而像CVS这样的早期开源集中式版本控制系统,由于设计上的问题,可能会出现文件提交不完整或版本库损坏的情况。SVN则修正了这些问题,成为目前使用最广泛的集中式版本控制系统。
除了免费的外,还有收费的集中式版本控制系统,比如IBM的ClearCase(以前是Rational公司的,被IBM收购了),特点是安装比Windows还大,运行比蜗牛还慢,能用ClearCase的一般是世界500强,他们有个共同的特点是财大气粗,或者人傻钱多。微软自己也有一个集中式版本控制系统叫VSS,集成在Visual Studio中。由于其反人类的设计,连微软自己都不好意思用了。分布式版本控制系统除了Git以及促使Git诞生的BitKeeper外,还有类似Git的Mercurial和Bazaar等。这些分布式版本控制系统各有特点,但最快、最简单也最流行的依然是Git!

1.3 安装Git

尽管Git最初是在Linux上开发的,并且很长一段时间内仅限于Linux和Unix系统,但随着时间的推移,它逐渐被移植到了Windows平台上。如今,Git已经能够在Linux、Unix、Mac和Windows等主流操作系统上流畅运行。
要开始使用Git,首先你需要在你的操作系统上安装它。根据你的当前平台,请参照以下指导进行安装:

1.3.1 在Linux系统上安装Git

首先,你可以通过在终端中输入git命令来检查系统是否已安装Git。如果Git未安装,系统将可能显示类似以下的提示信息:
这表明你的系统建议你使用sudo apt-get install git命令来安装Git。这是对于基于Debian的系统(如Ubuntu)的标准安装方法。
如果你使用的是较旧的Debian或Ubuntu版本,可能会建议使用sudo apt-get install git-core命令,因为在过去,Git被称为git-core以避免与另一个名为GNU Interactive Tools(GIT)的软件冲突。随着时间的推移,GIT被重新命名为gnuit,而git-core则正式更名为git
对于非Debian系的Linux发行版,安装Git的方法可能会有所不同。一些发行版可能使用yumdnf作为包管理工具,而不是apt-get。在这些系统上,你可以使用相应的命令来安装Git,如sudo yum install gitsudo dnf install git
如果你的Linux发行版没有提供Git的包,或者你想要安装最新版本的Git,你可以从Git官方网站下载源码进行手动安装。下载源码包后,解压并进入解压后的目录,然后依次执行以下命令:

1.3.2 在MacOS上安装Git

如果你正在使用Mac进行开发工作,安装Git有两种常用的方法。
方法一:使用Homebrew安装
首先,你需要安装Homebrew,它是一个Mac OS上的包管理器。安装Homebrew后,你可以通过它轻松地安装Git。以下是安装步骤:
  1. 打开终端(Terminal)。
  1. 访问Homebrew的官方网站:http://brew.sh/,并按照页面上的指导安装Homebrew。
  1. 安装完成后,在终端中输入以下命令来安装Git:
方法二:通过Xcode安装
Xcode是苹果官方提供的集成开发环境(IDE),它包含了Git。不过,Git并不会随着Xcode的安装而自动安装,需要手动启用。以下是安装步骤:
  1. 打开App Store,搜索并安装Xcode。
  1. 安装完成后,打开Xcode。
  1. 在Xcode的菜单栏中,选择“Xcode” -> “Preferences”。
  1. 在弹出的窗口中,找到“Downloads”选项卡。
  1. 在“Downloads”选项卡中,找到“Command Line Tools”选项,并点击旁边的“Install”按钮。
  1. Xcode会下载并安装Command Line Tools,其中包含了Git。

1.3.3 在Windows上安装Git

在Windows上使用Git,安装过程相对直观和简单。你可以直接从Git的官方网站下载适用于Windows的安装程序。以下是安装步骤:
  1. 访问Git官方网站:https://git-scm.com/download/win
  1. 在页面上,你会看到几个下载选项。通常,对于大多数用户来说,选择“Git for Windows”是最合适的。
  1. 下载完成后,运行安装程序。它将引导你完成安装过程。通常,你可以按照默认设置进行安装,除非你有特定的需求需要更改安装目录或选择其他组件。
  1. 安装完成后,Git会自动添加到你的系统路径中,这意味着你可以在任何目录下打开命令提示符或PowerShell,并直接使用git命令。
  1. 验证Git是否成功安装,你可以按下Win + R组合键打开运行对话框,输入cmd并按回车键,打开命令提示符。然后,在命令提示符中输入git --version。如果成功安装,它将显示Git的版本号。
  1. 另外,Git for Windows还提供了一个名为“Git Bash”的终端模拟器,它模拟了一个类似Unix的环境,并包含了常用的Unix命令。你可以在开始菜单中找到“Git”文件夹,并点击“Git Bash”来启动它。如果你看到这个类似命令行窗口的界面,那么Git就已经成功安装并可以使用了!
安装Git并完成基本的系统设置之后,确实需要配置一些全局设置,以便Git知道每次提交代码时的作者信息。这些信息包括你的用户名和电子邮件地址。通过执行以下命令,你可以轻松设置这些信息:
在这里,--global 参数表示这些设置将应用于这台计算机上的所有Git仓库。如果你只想为特定的仓库设置用户名和电子邮件地址,可以省略 --global 参数,并在仓库目录中执行这些命令。
Git使用这些信息来标识每次提交的作者,这对于协作开发以及跟踪代码变更历史非常有用。此外,这些设置还有助于建立信任,因为它们可以帮助确认提交是否来自已知和可信的开发者。
当然,如果你担心有人冒用他人的身份进行提交,Git提供了一些工具和策略来检测和解决这类问题。例如,你可以通过检查提交者的电子邮件地址是否与已知的开发者地址匹配,或者使用GPG签名来验证提交的来源。
在配置完这些全局设置之后,你就已经为使用Git做好了准备,可以开始创建仓库、提交代码以及与其他开发者协作了。

二、版本管理

2.1 创建版本库

版本库,也常被称为仓库,英文名为repository,它实质上是一个目录。在这个目录中,所有的文件都受到Git的管理。Git能够跟踪这些文件的每一次修改和删除,从而让你能够在任何时间点回溯历史,或者在将来某个时刻恢复到某个特定的状态。
要创建一个新的版本库,首先你需要选择一个合适的位置,并在那里创建一个空目录。例如,你可以使用mkdir命令在命令行中创建一个名为gitlearning的新目录,然后使用cd命令切换到这个新目录。
pwd命令用于显示当前目录。在我的电脑上,这个仓库位于/e/Develop/gitleanring
如果你使用的是Windows系统,为了避免可能的问题,建议确保目录名(包括其父目录)不包含中文字符。
接下来,你可以使用git init命令来初始化这个目录,将其转变为一个Git可以管理的仓库。
执行这个命令后,Git会立即在这个目录下创建一个名为.git的子目录,用于存储所有关于版本控制的元数据和对象数据。这个.git目录是Git仓库的核心,它包含了所有关于仓库历史和状态的信息。
请注意,.git目录默认是隐藏的,因此你可能不会立即在文件浏览器中看到它。如果你想在命令行中查看这个目录,你可以使用ls -ah命令,它会显示当前目录下的所有文件和目录,包括隐藏的文件和目录。
一旦你初始化了Git仓库,你就可以开始使用Git的各种命令来管理你的代码和文件了。

2.1.1 把文件添加到版本库

首先,需要明确的是,所有的版本控制系统,包括Git在内,主要跟踪的是文本文件的变动。这是因为版本控制系统能够读取文本文件的内容,记录每一行的变化,如添加、删除或修改。这种粒度级的追踪对于代码管理、协作和回滚等操作至关重要。
对于文本文件,如TXT、网页源代码、各种编程语言的代码等,版本控制系统能够提供详尽的变动历史。例如,它能够告诉你某个文件在第5行增加了一个单词“Linux”,或者在第8行删除了一个单词“Windows”。这种信息对于理解代码演进、调试和协作非常有帮助。
然而,对于图片、视频等二进制文件,版本控制系统的能力就相对有限了。虽然它们也能够管理这些文件,但无法跟踪文件内部的具体变化。版本控制系统只能记录二进制文件整体的变动,比如从100KB增加到120KB。至于文件内部到底发生了什么变化,版本控制系统无法得知。
值得注意的是,Microsoft的Word文档等某些二进制格式文件,在版本控制系统中同样无法实现精细的变动追踪。因此,在实际使用版本控制系统时,推荐使用纯文本格式来编写文件,以便更好地利用版本控制系统的功能。
此外,由于文本文件具有编码属性,不同的编码方式可能会导致在不同平台或软件之间出现兼容性问题。为避免这类问题,建议使用标准的UTF-8编码来编写文本文件。UTF-8编码具有广泛的支持性和兼容性,能够处理各种语言的字符,且不会引起冲突。
特别提醒使用Windows系统的用户,尽量避免使用Windows自带的记事本编辑器来编辑文本文件。这是因为记事本在处理UTF-8编码文件时,会在文件开头添加额外的字节(0xefbbbf,十六进制),这可能会导致在其他平台或软件中显示异常或引发错误。为了避免这些问题,建议使用其他更专业的文本编辑器,如Visual Studio Code等。这些编辑器不仅功能强大,而且免费可用,能够提供更好的文本编辑和版本控制体验。
言归正传,现在我们编写一个readme.txt文件,内容如下:
要在Git仓库中管理文件,你需要将文件放置在仓库的目录中(或其子目录中)。这是因为Git需要知道哪些文件在其管理范围内。将文件添加到Git仓库需要两个步骤。
第一步:使用git add命令
在命令行中输入以下命令,将readme.txt文件添加到Git仓库:
如果命令执行成功,通常不会有任何输出。这是Unix哲学中的“没有消息就是好消息”的体现,表示文件已成功添加到暂存区。
第二步:使用git commit命令
接下来,你需要使用git commit命令将暂存区的文件提交到Git仓库:
这个命令中的-m参数后面跟的是本次提交的说明。这个说明对于你和其他的开发者来说都是非常重要的,因为它可以帮助你们了解这次提交做了哪些变更。虽然你可以不输入-m参数和提交说明来执行git commit命令,但我们强烈建议你总是添加有意义的提交说明。这不仅有助于你自己回顾历史记录,也有助于其他开发者理解你的工作。
git commit命令执行成功后,你会看到类似以下的输出:
这表示:
  • [master (root-commit) eaadf4e]:这是提交的唯一标识符,每次提交都会有一个独特的ID。
  • 1 file changed:有1个文件被改动。
  • 2 insertions(+):这个文件增加了2行内容。
  • create mode 100644 readme.txt:新创建了一个名为readme.txt的文件,并设置了其权限模式。
为什么Git添加文件需要addcommit一共两步呢?因为commit可以一次提交很多文件,所以你可以多次add不同的文件,比如:

2.2 简单的文件修改与提交

经过前面的步骤,我们已经成功地向Git仓库中添加并提交了一个readme.txt文件。现在,我们继续对该文件进行修改,以展示Git如何处理文件的变更。
首先,我们修改了readme.txt文件的内容,将其从“Git is a version control system.”改为“Git is a distributed version control system.”。
为了查看当前仓库的状态,我们运行了git status命令:
这个输出告诉我们,readme.txt文件已经被修改,但这些修改还没有被添加到暂存区,因此还没有准备好提交。
为了查看具体修改了哪些内容,我们使用了git diff命令:
这个命令显示了readme.txt文件的差异,显示的格式正是Unix通用的diff格式,从中我们可以看到在第一行添加了“distributed”这个单词。
知道了对readme.txt作了什么修改后,再把它提交到仓库就放心多了,接下来,我们将修改添加到暂存区,准备提交:
再次运行git status,我们可以看到修改已经被添加到暂存区,准备提交:
最后,我们提交这些修改:
提交后,再次运行git status确认仓库状态:
Git告诉我们当前没有需要提交的修改,工作目录是干净的,意味着所有的变更都已经被提交到了仓库中。这样,我们就完成了一次文件的修改和提交过程。

2.3 版本回退

跟着之前的教程,我相信你已经熟练掌握了如何修改文件并提交这些修改到Git版本库中。接下来,我们再次练习一下这个过程。首先,我们要修改readme.txt文件,使其内容如下:
完成修改后,我们将这些更改提交到Git版本库中:
每当你对文件进行了一系列的修改,并认为这些修改达到了一个可以保存的状态时,你可以使用git add命令将这些修改添加到暂存区,然后使用git commit命令将这些修改提交到Git版本库中。这就像是在游戏中每通过一个关卡就保存一次进度,或者在打Boss前手动存档,以便在失败时能够从最近的保存点重新开始。
现在,让我们回顾一下readme.txt文件的提交历史:
版本1:wrote a readme file
版本2:add distributed
版本3:append GPL
当然,对于一个包含数千行的文件,我们很难记住每次提交都做了哪些更改。这正是版本控制系统发挥作用的地方。在Git中,我们可以使用git log命令来查看提交历史记录:
通过git log命令,我们可以看到从最近到最远的提交日志。在这个例子中,我们可以看到三次提交,最近的一次是“append GPL”,上一次是“add distributed”,而最早的一次是“wrote a readme file”。
如果嫌输出信息太多,看得眼花缭乱的,可以试试加上--pretty=oneline参数:
友情提示一下,你所看到的那串如“1094adb…”的数字是commit id(版本号)。与SVN不同,Git的commit id并不是像1、2、3这样递增的数字。它是一个通过SHA1算法计算得出的非常大的数字,以十六进制的形式表示。你看到的commit id与我所看到的肯定是不同的,每个人的commit id都是唯一的。
为什么commit id需要使用这么一大串数字来表示呢?这主要是因为Git是一个分布式的版本控制系统。在后续的学习中,我们将研究多人在同一个版本库中协同工作的情况。如果大家都使用1、2、3这样的数字作为版本号,那么很容易就会发生版本冲突。而使用SHA1算法生成的这一大串数字作为commit id,可以确保在全球范围内都是唯一的,从而避免了版本冲突的问题。这样的设计使得Git成为一个强大且灵活的版本控制系统。
好的,现在我们将启动时光穿梭机,回退到readme.txt的上一个版本,也就是“add distributed”的那个版本。在Git中,HEAD表示当前版本,即最新的提交。因此,上一个版本可以通过HEAD^来表示,上上一个版本是HEAD^^,依此类推。当然,如果我们要回退100个版本,写100个^显然不太现实,所以Git提供了HEAD~100这样的简写。
要回退到上一个版本,我们可以使用git reset命令。例如:
这条命令会将当前版本(即“append GPL”版本)回退到上一个版本(即“add distributed”版本)。--hard参数的作用是丢弃当前版本的所有更改,直接回退到指定版本。这意味着所有未提交的改动都将丢失,所以在使用时要特别小心。
现在,我们可以检查readme.txt的内容,确认是否回退到了正确的版本:
输出应该是:
果然,内容被还原到了“add distributed”版本。
但是,如果我们想继续回退到更早的“wrote a readme file”版本,我们应该怎么做呢?在回退之前,让我们先用git log命令查看一下当前版本库的状态:
输出会显示所有提交的历史记录。找到你想要回退到的版本的commit id(例如eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0),然后使用git reset命令回退到该版本:
或者,如果你只记得commit id的前几位,你也可以使用这些前几位数字来指定要回退到的版本。Git会尝试找到与这些数字匹配的最近的commit id。
现在,如果你再次查看readme.txt的内容,你会看到它已经回退到了“wrote a readme file”版本的内容。
需要注意的是,一旦使用git reset --hard命令回退到某个版本,之前的提交记录将不再可见(除非你还没有关闭命令行窗口,并且还能够找到之前的commit id)。这意味着如果你不小心回退到了一个错误的版本,并且没有记录下之前的commit id,那么你可能无法再找回之前的版本了。因此,在使用git reset命令时,一定要小心谨慎。
Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD从指向append GPL
notion image
改为指向add distributed
notion image
然后顺便把工作区的文件更新了。所以你让HEAD指向哪个版本号,你就把当前版本定位在哪。
如果你不小心回退到了一个版本并关掉了电脑,然后后悔想要恢复到新版本,但又找不到新版本的commit id,这时不必过于焦虑。在Git中,你总是有机会恢复到之前的状态。
Git提供了一个非常有用的命令git reflog,它会记录你的每一次命令操作,包括你回退版本的操作。通过git reflog,你可以找到之前版本的commit id,然后再次使用git reset命令恢复到那个版本。
执行git reflog命令后,你会看到类似以下的输出:
这个输出显示了你的提交历史,包括每次提交的commit id和对应的操作描述。从输出中,你可以看到“append GPL”的commit id是1094adb。现在,你可以使用这个commit id来恢复到那个版本:
因此,即使你关闭了电脑,也不必担心无法恢复到之前的版本。只要你还记得大致的操作顺序,git reflog命令就能帮助你找到正确的commit id,并让你乘坐“时光机”回到过去。

三、文件管理

3.1 工作区和暂存区

Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念。
先来看名词解释。
工作区(Working Directory)
就是你在电脑里能看到的目录,比如之前创立的gitearning文件夹就是一个工作区。
notion image
版本库(Repository)
工作区中有一个名为.git的隐藏目录,这并不是工作区的一部分,而是Git的版本库所在地。Git的版本库中存储了许多关键信息,其中最重要的是暂存区(也称为index)。暂存区是一个用于准备提交的区域,你可以将修改后的文件添加到暂存区,然后再一起提交到Git仓库中。
除了暂存区,Git还为我们自动创建了一个名为master的分支。分支是Git中用于并行开发的功能,每个分支都代表了一个独立的开发线路。master分支通常是默认的主分支,用于存储稳定的代码。
在Git中,还有一个名为HEAD的指针,它始终指向当前所在的分支的最新提交。通过HEAD,Git能够知道当前的工作是基于哪个版本的,从而确保每次提交和回退操作都是准确无误的。
notion image
当我们想要把文件添加到Git版本库中时,这个过程分为两个步骤:
  1. 使用git add命令将文件添加到暂存区。这一步是将你对文件的修改标记为准备提交的状态。
  1. 使用git commit命令提交暂存区的更改。这一步实际上是将暂存区中的所有修改内容正式提交到Git的版本库中。
值得注意的是,当我们首次创建一个Git版本库时,Git会自动为我们创建一个名为master的分支。因此,在默认情况下,执行git commit命令时,实际上是向master分支提交更改。
现在,我们再练习一遍,先对readme.txt做个修改,比如加上一行内容:
然后,在工作区新增一个LICENSE文本文件(内容随便写)。
先用git status查看一下状态:
Git非常清楚地告诉我们,readme.txt被修改了,而LICENSE还从来没有被添加过,所以它的状态是Untracked
现在,使用两次命令git add,把readme.txtLICENSE都添加后,用git status再查看一下:
现在,暂存区的状态就变成这样了:
notion image
所以,git add命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit就可以一次性把暂存区的所有修改提交到分支。
一旦提交后,如果你又没有对工作区做任何修改,那么工作区就是“干净”的:
现在版本库变成了这样,暂存区就没有任何内容了:
notion image

3.2 管理修改

Git相较于其他版本控制系统的优势在于其独特的设计:Git跟踪并管理的是修改,而非文件。这一特性让Git在处理版本控制时表现得更为出色。
理解“修改”这一概念至关重要。在Git中,无论是增加、删除还是更改一行内容,甚至创建一个新文件,都被视为一个修改。Git关注这些底层的变动,而非文件本身。
为了深入理解这一点,我们可以通过一个简单的实验来演示。首先,我们对readme.txt文件进行了一次修改,并添加了新内容。
然后,使用git add命令将这次修改放入暂存区,准备提交。
然而,在提交之前,我们对同一文件进行另一次修改。
现在我们执行git commit命令时,只有第一次的修改被提交了,第二次的修改并未包含在内。
这是因为Git管理的是修改,而不是文件。当我们使用git add命令时,Git会将工作区的修改放入暂存区,准备提交。然而,如果我们在git add之后再次修改文件,这些新的修改并不会自动进入暂存区。因此,当我们执行git commit命令时,只有暂存区中的修改会被提交。
这一特性使得Git在处理版本控制时具有更高的灵活性和准确性。它可以让我们精确地控制哪些修改应该被提交,哪些修改应该被保留在工作区。同时,由于Git跟踪的是修改而非文件,它在处理大量文件或大型项目时也能保持高效的性能。
提交后,用git diff HEAD -- readme.txt命令可以查看工作区和版本库里面最新版本的区别:
可见,第二次修改确实没有被提交。

3.3 撤销修改

现在假设这样一个场景:
深夜两点的加班狂欢仍在继续,咖啡杯旁的readme.txt文件见证了我工作报告的进展。在键盘的敲击声中,我不小心加入了一行可能会让我后悔终生的文字:“My stupid boss still prefers SVN.”
就在我准备提交这份报告,将我的辛勤努力化为版本库中一串美丽的哈希值时,咖啡的香气和屏幕的冷光让我瞬间清醒。我瞪大了眼睛,看着屏幕上的那行文字,仿佛看到了老板愤怒的脸庞和即将消失的奖金。
幸好,Git这个救星总是在关键时刻出现。我迅速执行了git status来查看当前的状态:
Git告诉我,我的修改还没有被放入暂存区,而git checkout -- <file>这个魔法命令可以帮我撤销这些修改。我深吸了一口气,轻轻敲下命令:
仿佛时间倒流,readme.txt文件瞬间回到了它最纯洁的状态,那句冒犯之语消失得无影无踪。我再次查看文件内容,确认一切已经恢复如初:
git checkout -- file命令中的--很重要,没有--,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到git checkout命令。
现在让这个场景更凶险些:
凌晨三点,我依然在键盘上飞舞,咖啡因与我并肩作战,对抗着即将到来的截止日期。不幸的是,我不仅在readme.txt文件中加入了荒谬的内容,并且用git add命令把它放入了暂存区。
我瞪大了眼睛,试图从这段文字中找出任何理智的迹象,但一切似乎都太晚了。然而,在我准备绝望地按下git commit按钮之前,我突然意识到——还有机会!我可以用git status来查看当前的状态:
看到这条信息,我仿佛看到了希望的曙光。Git告诉我,我可以用git reset HEAD <file>命令来撤销暂存区的修改。这就像是一个时间门,可以让我回到做出错误决定之前的状态。
我迅速执行了命令:
git reset命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD时,表示最新的版本。
我再次运行git status来确认状态:
还记得如何丢弃工作区的修改吗?
readme.txt文件再次变得清洁无瑕,就像什么都没有发生过一样。我长长地松了一口气,感觉整个世界都重新回到了秩序之中。
继续让头脑发昏:
现在是凌晨四点,咖啡已经见底,为了赶上DeadLine我已逐渐神志不清,不但改错了东西,还从暂存区提交到了版本库,怎么办呢?还记得版本回退一节吗?可以回退到上一个版本。不过,这是有条件的,就是还没有把自己的本地版本库推送到远程。还记得Git是分布式版本控制系统吗?我们后面会讲到远程版本库,一旦把stupid boss提交推送到远程版本库,那……

3.4 删除文件

在Git中,删除操作也被视为一种修改。下面我们将通过实际操作来演示这一过程。首先,我们向Git中添加一个新的文件test.txt并提交:
执行上述命令后,test.txt文件将被添加到Git的版本库中,并附带一个提交信息,表明我们添加了这个文件。
在大多数情况下,你可能会直接在文件管理器中删除不再需要的文件,或者使用rm命令来删除它:
当你执行rm命令后,Git会意识到文件已被删除,因此工作区与版本库之间会出现不一致。你可以通过git status命令来查看哪些文件已被删除:
此时,你有两个选择。如果你确实想从版本库中删除这个文件,你应该使用git rm命令,并提交这次删除操作:
执行上述git rmgit commit命令后,test.txt文件将从Git的版本库中彻底删除。现在,该文件的所有历史记录都将被移除,并且无法再恢复(除非使用Git的引用日志或其他恢复技术)。
第二种情况:你不小心删错了文件,但不必过于担心,因为Git的版本库中仍然保留着该文件的历史记录。如果你意识到误删了文件并希望恢复到最新版本,可以使用git checkout命令。这个命令实际上是使用版本库中的版本替换工作区的版本,无论工作区中的文件是被修改还是被删除,都可以通过git checkout进行“一键还原”。

四、远程仓库

到目前为止,我们已经掌握了在Git仓库中进行文件历史回溯的技巧,这让你无需再担心文件的备份或丢失问题。然而,有些曾经使用过集中式版本控制系统如SVN的朋友可能会认为,这些功能在SVN中也同样存在,从而质疑Git的独特价值。
确实,如果仅仅是在一个仓库内管理文件历史,Git和SVN之间的差别并不明显。但为了让你的Git学习之旅物超所值,并且展示Git相对于其他版本控制系统的优势,我们将深入探讨Git的一个核心功能——远程仓库。这仅仅是Git众多强大功能之一,后续还将介绍更多。
Git作为分布式版本控制系统,其核心理念是将同一个仓库分布到不同的机器上。这一分布过程始于一个原始版本库,随后其他机器可以“克隆”这个原始仓库。值得注意的是,每台机器上的仓库都是完整的,并没有主从之分。
你可能会好奇,远程仓库是否至少需要两台机器才能实现?如果你只有一台电脑,是否还能利用远程仓库的功能呢?答案是肯定的。尽管在单一设备上克隆多个仓库在技术上可行,但这样做并没有实际意义,因为一旦硬盘出现故障,所有仓库都会受到影响。因此,我们不会推荐在一台电脑上进行多个仓库的克隆操作。
在实际应用中,通常会选择一台电脑作为服务器,保持24小时在线状态。其他开发者则从这台“服务器”仓库克隆一份到自己的电脑上,各自进行提交操作,并将这些提交推送到服务器仓库。同时,他们也会从服务器仓库中拉取其他开发者的提交,以保持代码的同步。
虽然自己搭建Git服务器也是一种选择,但对于初学者来说,这可能会过于复杂。幸运的是,世界上有许多提供Git仓库托管服务的平台,其中最著名的就是GitHub。通过注册一个GitHub账号,你就可以免费获得Git远程仓库,从而轻松实现代码的托管和协作。
在开始之前,请确保你已经注册了GitHub账号,并且安装了Git。SSH连接的设置是为了确保你的本地Git仓库与GitHub仓库之间的通信是加密和安全的。
步骤一:生成SSH密钥对
  1. 打开终端(Windows用户请打开Git Bash)。
  1. 输入命令 $ ssh-keygen -t rsa -C "youremail@example.com" 来生成SSH密钥对。请将youremail@example.com替换为你的实际邮箱地址。
  1. 按下回车键,按照提示完成密钥对的生成。期间,你可能会被要求输入一个密码短语(passphrase),这是为了给你的私钥增加一层保护。如果你不想设置密码短语,可以直接按回车键跳过。
步骤二:添加SSH公钥到GitHub
  1. 在你的用户主目录下,找到.ssh文件夹。在该文件夹中,你应该可以看到id_rsa(私钥)和id_rsa.pub(公钥)两个文件。
  1. 使用文本编辑器打开id_rsa.pub文件,复制里面的内容。
  1. 登录你的GitHub账号,点击右上角的头像,选择“Settings”(设置)。
  1. 在左侧导航栏中,选择“SSH and GPG keys”。
  1. 点击“New SSH key”按钮,进入添加SSH密钥的页面。
  1. 在“Title”字段中,输入一个描述性的名称(例如,“个人电脑”或“工作电脑”)。
  1. 在“Key”字段中,粘贴你之前从id_rsa.pub文件中复制的内容。
  1. 点击“Add SSH key”按钮,完成SSH公钥的添加。
notion image
步骤三:测试SSH连接
在终端中,输入命令 $ ssh -T git@github.com 来测试SSH连接。如果连接成功,你应该会看到一条消息,显示你已经成功连接到了GitHub。
完成以上步骤后,你就可以使用SSH协议来推送(push)和拉取(pull)代码到你的GitHub仓库了。SSH连接不仅更安全,而且在某些情况下,速度也可能更快。

4.1 连接远程仓库

连接本地仓库和Github仓库
现在的情景是,你已经在本地创建了一个Git仓库后,又想在GitHub创建一个Git仓库,并且让这两个仓库进行远程同步,这样,GitHub上的仓库既可以作为备份,又可以让其他人通过该仓库来协作,真是一举多得。
首先,我们需要在GitHub上创建一个新的仓库。登录到GitHub后,在右上角的加号按钮中找到并点击“new repository”按钮。在弹出的窗口中,为仓库命名为gitlearning,其他设置保持默认,然后点击“Create repository”按钮。至此,一个名为gitlearning的新仓库已经在GitHub上成功创建,但此时它是空的。
notion image
GitHub提示我们可以从该仓库克隆出一个新的仓库,或者将已有的本地仓库与之关联,并将本地仓库的内容推送到GitHub仓库。这里,我们选择将已经在本地创建并配置好的gitlearning仓库与GitHub上的仓库进行关联。
在本地gitlearning仓库的目录下,打开命令行工具,并输入以下命令来添加远程仓库:
请确保将上述命令中的username替换为您自己的GitHub账户名。这样,我们就将远程仓库命名为origin,这是Git的默认命名方式,但也可以选择其他名称。
接下来,我们可以将本地仓库的所有内容推送到远程仓库。在命令行中输入以下命令:
执行此命令后,Git将开始将本地master分支的内容推送到远程仓库。推送过程中,Git会计算对象的差异、进行压缩,并将这些对象写入远程仓库。一旦推送成功,远程仓库将与本地仓库保持一致。
由于这是第一次将本地master分支推送到远程仓库,我们使用了-u参数。这将不仅把本地master分支的内容推送到远程的master分支,还会将本地master分支与远程的master分支关联起来。这样,在未来的推送或拉取操作中,我们可以简化命令。
推送成功后,我们可以在GitHub的页面上立即看到远程仓库的内容已经与本地仓库完全同步。从现在起,每当我们在本地进行提交操作时,只需执行以下命令:
就可以将本地master分支的最新修改推送到GitHub上的远程仓库。这样,我们就成功地设置了一个真正的分布式版本库,既可以在本地进行开发和修改,又可以将修改同步到远程仓库进行备份和协作。
SSH警告
当你首次使用Git的clonepush命令与GitHub进行交互时,通常会遇到一个关于主机认证的警告信息。这是因为Git通过SSH协议与远程仓库进行通信,而SSH在首次与远程服务器建立连接时,会要求你验证服务器公钥的指纹,以确保你正在连接到的确实是预期的服务器。
这个警告信息表明,Git无法确定github.com(及其相关的IP地址)的身份是否真实。它会显示RSA密钥的指纹,并询问你是否确信要继续连接。此时,你可以选择输入yes并按回车,表示你信任这个服务器的身份,并希望Git将它的公钥添加到已知主机列表中。
一旦你确认并输入yes,Git会输出一条警告信息,告诉你github.com的RSA密钥已被永久添加到已知主机列表中。这意味着在未来的连接中,你将不再收到此警告,因为Git已经记住了这个服务器的公钥。
如果你对安全性有高度关注,担心有人可能冒充GitHub服务器,那么在输入yes之前,你可以对照GitHub官方提供的RSA密钥指纹信息,以确保SSH连接所显示的信息与之一致。这样,你可以确保你的连接是安全的,并且确实与GitHub的服务器建立了连接。
删除远程库
如果你在添加远程仓库时输入了错误的地址,或者出于某种原因想要解除本地仓库与远程仓库的关联,你可以使用git remote rm <name>命令。在执行此操作之前,为了确认当前配置的所有远程仓库信息,建议首先使用git remote -v命令查看。
运行git remote -v后,你将看到类似以下的输出,显示了所有配置的远程仓库及其对应的URL:
在这个例子中,origin是远程仓库的名称,而后面的URL则是与之关联的地址。如果你想要删除名为origin的远程仓库,可以执行以下命令:
请注意,这里的“删除”操作实际上只是解除了本地仓库与远程仓库之间的绑定关系,并不会物理上删除远程仓库本身或其包含的数据。远程仓库依然存在于GitHub等托管服务上,只是本地仓库不再与之关联。如果你想要彻底删除远程仓库,你需要登录到相应的托管服务(如GitHub),并在其后台页面中找到相应的删除按钮来执行删除操作。

4.2 从远程库克隆

上次我们讨论了如何在已有本地库的情况下关联远程库。然而,如果我们从零开始开发一个新项目,那么最佳实践通常是先创建远程库,然后从远程库克隆到本地。
首先,你需要登录到GitHub,并创建一个新仓库。给这个仓库命名为gitskills,并保持其他设置不变。
我们勾选Initialize this repository with a README,这样GitHub会自动为我们创建一个README.md文件。
创建远程仓库后,接下来我们需要将这个远程仓库克隆到本地。为此,你需要打开终端或命令行界面,并使用git clone命令,后跟远程仓库的URL。这个URL可以在你创建仓库后的GitHub页面上找到。
命令如下:
请确保将username替换为你的GitHub用户名。执行这个命令后,Git会自动从远程仓库下载所有文件并创建一个本地仓库目录,目录名称默认与远程仓库的名称相同(在这个例子中是“gitskills”)。
进入gitskills目录看看,已经有README.md文件了。
现在,你已经拥有了一个与远程仓库同步的本地仓库副本。你可以在这个本地仓库中进行开发,提交更改,然后使用git push命令将更改推送回远程仓库。
这种方式的好处是,从一开始你就有一个与远程仓库同步的本地工作副本,这有助于团队协作和版本控制。同时,由于远程仓库已经存在,其他人也可以更容易地访问和贡献代码。
你可能已经注意到,GitHub提供了多种仓库访问地址格式,其中包括https://github.com/username/gitskills.git这样的形式。实际上,Git是一个支持多种协议的版本控制系统,默认的git://协议使用SSH进行通信,但也可以使用https://等其他协议。
使用https://协议的一个主要缺点是速度可能相对较慢,特别是在处理大量数据或频繁进行通信时。此外,每次使用https://协议推送更改时,系统通常会要求你输入GitHub账户的密码,这可能会增加操作的复杂性。
然而,有些公司或网络环境可能仅允许通过http://端口进行通信,这样的情况下,即使SSH协议不可用,你仍然可以通过https://协议与远程仓库进行交互。在这种情况下,虽然每次推送都需要输入密码,但这也是与远程仓库进行通信的唯一可行方式。

五、分支管理

分支在版本控制系统中,可以被形象地比喻为科幻电影中的平行宇宙。当我们谈论Git的分支时,我们实际上是在谈论代码库的不同路径或版本。当两个平行宇宙(即分支)在某个时间点合并时,你能够结合两者的优势,就像同时掌握了Git和SVN一样。
在实际开发中,分支的用途显得尤为重要。设想一下,你正在开发一个新功能,预计需要两周时间完成。在开发的第一周,你可能只完成了50%的代码。如果这时你将这些不完整的代码提交到主分支,那么其他开发者可能会因为不完整的代码库而无法正常工作。然而,如果你选择等待所有代码都写完再提交,又可能会面临丢失每天进度的风险。
这时,Git的分支功能就能派上用场了。你可以创建一个属于自己的分支,在这个分支上自由地进行开发,而不用担心影响其他开发者。你可以随时提交你的代码,而不用担心破坏主分支的稳定性。当新功能开发完成后,你可以将这个分支与主分支合并,从而安全地将你的工作成果整合到项目中。
与其他版本控制系统(如SVN)相比,Git的分支管理功能具有显著的优势。在Git中,创建、切换和删除分支的操作非常迅速,几乎可以在一秒钟内完成,无论你的版本库包含多少个文件。这使得分支在Git中成为了一个实用且高效的功能,而不是像在其他版本控制系统中那样被束之高阁。

5.1 创建与合并分支

在版本控制中,Git通过维护一个时间线来记录每次的提交操作。这个时间线其实就是一个分支。在Git的默认设置中,只有一条这样的时间线存在,它被称为主分支,或者更常见地,被称为master分支。HEAD在Git中是一个特殊的指针,但它并不直接指向一个提交,而是指向当前活动的分支。一般来说,master分支就是当前的活动分支,因此HEAD实际上是指向master的。这意味着,当我们谈论HEAD时,我们其实是在谈论当前的活动分支以及该分支上的最新提交。
在最初的状态,master分支是一个单一的线性时间线。Git使用master指针来标记最新的提交,而HEAD指针则指向master,这样我们就可以清晰地知道当前的活动分支以及该分支上的最新提交点。
notion image
每当进行一次提交操作,master分支都会沿着时间线向前移动一步。这意味着,随着你的不断提交,master分支所代表的时间线(或者说分支线)会逐渐增长。
当你想创建一个新的分支,比如dev时,Git会创建一个新的指针叫做dev。这个新的dev指针会指向与master分支相同的提交点。然后,Git会将HEAD指针重新定位到dev,表示当前的工作分支已经切换到了dev分支上。这样,你就可以在dev分支上进行新的提交操作,而不会影响到master分支上的内容。
notion image
现在,当你在工作区进行修改并提交时,这些操作将针对dev分支进行。例如,当你提交一次新的更改后,dev指针会沿着时间线向前移动一步,指向这次新的提交。然而,master指针则保持不变,它仍然指向之前的提交点,表示master分支上的内容没有发生变化。这样,你就可以在dev分支上进行独立的开发工作,而不会影响到master分支的稳定性。
notion image
一旦在dev分支上的工作完成,你可能会想要将这些更改合并回master分支。Git提供了几种合并策略,但最简单和最直接的方法是执行一个快进合并(fast-forward merge)。在这种情况下,Git会将master分支的指针直接移动到dev分支的当前提交上,从而完成合并操作。这实际上意味着master分支现在包含了dev分支上的所有更改,并且两个分支的指针都指向相同的提交点。这种合并方式之所以被称为“快进”,是因为它不需要创建任何新的提交或解决任何合并冲突,而是简单地将一个分支的指针移动到另一个分支的当前位置。
notion image
Git合并分支的操作通常是非常迅速的。在大多数情况下,它仅仅涉及更改指针的指向,而工作区的内容通常不会发生变化。
完成分支合并后,如果你不再需要dev分支,可以选择将其删除。删除dev分支意味着移除指向该分支的指针。一旦指针被删除,dev分支就不再存在,你将只剩下master分支作为唯一的活跃分支。这种操作在Git中通常是安全的,只要确保没有其他人正在该分支上进行工作,因为删除分支会永久性地丢失其指向的提交历史记录。
notion image
下面,我们开始进行实际的Git操作。
首先,我们将创建一个名为dev的新分支,并立即切换到这个分支上。可以使用以下命令:
git checkout命令加上-b参数的作用就是创建新分支并立即切换到该分支。这相当于执行了以下两条命令:
接下来,我们使用git branch命令来查看当前所有的分支,以及当前活动的分支。这个命令会列出所有的分支,并在当前活动的分支前面加上一个*号。
现在,我们已经在dev分支上了,可以开始在这个分支上进行工作。例如,我们修改readme.txt文件,添加一行文字:
然后,我们将这个修改提交到Git仓库。
完成dev分支上的工作后,我们可以切换回master分支,看看readme.txt文件的变化。
当我们切换回master分支时,会发现刚才在dev分支上做的修改并没有出现在master分支的readme.txt文件中。这是因为这两个分支的提交历史是独立的。dev分支上的提交并没有自动合并到master分支上。
notion image
为了让master分支也包含dev分支上的修改,我们需要进行分支合并操作。接下来,我们将演示如何将dev分支合并到master分支。使用以下命令执行合并操作:
git merge命令用于将指定的分支(在此例中为dev)合并到当前分支(在此例中为master)。合并完成后,通过查看readme.txt的内容,你会发现它与dev分支上的最新提交完全一致。
Git输出的Fast-forward信息表明这次合并采用了快进模式,意味着Git只是简单地将master分支的指针移动到dev分支的最新提交上,因此合并过程非常迅速。
虽然快进合并是最常见的合并方式,但Git也支持其他更复杂的合并策略,我们会在后续内容中探讨这些情况。
完成合并后,可以安全地删除dev分支,使用以下命令进行删除:
删除分支后,通过运行git branch命令查看当前存在的分支,你将只看到master分支:
由于创建、合并和删除分支在Git中都是非常快速的操作,因此鼓励开发者频繁地使用分支来隔离不同的工作流。在完成特定任务后,可以将这些分支合并回主分支(如master),然后删除它们。这种方法与直接在主分支上工作具有相同的最终效果,但提供了更高的灵活性和安全性。
有的朋友已经发现,git checkout命令在Git中扮演了多个角色,它既可以用于切换分支,又可以用于撤销对文件的修改,这可能会让初学者感到有些困惑。为了解决这个问题,并在语义上更清晰地区分这两个操作,最新版本的Git引入了新的git switch命令专门用于切换分支。
如果你想创建并立即切换到一个新的dev分支,你可以使用以下命令:
这里,-c选项告诉Git在切换的同时创建新分支。
如果你想直接切换到一个已经存在的分支,比如master,你可以简单地使用:

5.2 解决冲突

人生不如意之事十之八九,合并分支时同样也会遇到种种挑战。现在,让我们继续分支开发的旅程,并探索合并过程中可能遇到的问题。
首先,我们准备一个新的feature1分支,以便在上面进行开发工作。使用git switch -c命令可以创建并切换到新分支:
feature1分支上,我们修改readme.txt文件的最后一行,将其改为:“Creating a new branch is quick AND simple.”。然后,将这个修改提交到Git仓库:
接下来,我们切换到master分支,准备将feature1分支的改动合并进来:
在切换到master分支时,Git提醒我们当前的master分支比远程的master分支超前了1个提交。这是因为我们之前在master分支上做了修改并提交,而这些改动还没有推送到远程仓库。
现在,我们在master分支上对readme.txt文件的最后一行做了另一个修改,将其改为:“Creating a new branch is quick & simple.”。并提交这个改动:
现在,master分支和feature1分支都有了各自的修改,并且这些修改涉及到相同的文件。如果我们尝试合并feature1分支到master分支,Git将会检测到冲突,因为两个分支对同一行文本做了不同的修改。
notion image
在这种情况下,由于feature1分支和master分支对readme.txt文件的不同部分做了修改,Git无法自动执行“快速合并”。当尝试合并这两个分支时,Git会尝试合并各自的改动,但由于冲突的存在,合并过程失败了。
Git提示我们readme.txt文件存在冲突,并建议我们手动解决冲突后再提交。为了查看冲突的具体内容,我们可以使用git status命令:
查看readme.txt文件的内容,我们可以看到Git用特殊的标记(<<<<<<<=======>>>>>>>)标出了冲突的部分:
为了解决这个问题,我们需要手动编辑readme.txt文件,解决冲突的内容。在这个例子中,我们可以将冲突的部分修改为:
保存文件后,我们使用git add命令将解决冲突后的文件标记为已解决状态,并使用git commit命令完成合并提交:
这样,我们就成功解决了合并冲突,并将合并后的结果提交到了master分支。在解决冲突时,重要的是要确保合并后的代码能够正常工作,并且符合团队的编码规范。
现在,master分支和feature1分支变成了下图所示:
notion image
使用带有参数的 git log 命令,我们可以更详细地查看分支的合并历史,包括分支的创建、合并以及提交的内容。--graph 参数会以图形化的方式展示分支和合并的历史,--pretty=oneline 参数会将每个提交的信息压缩成一行显示,而 --abbrev-commit 参数则会将提交哈希值缩短以便于阅读。
执行以下命令:
输出可能是这样的:
这个输出展示了一个可视化的分支合并历史。在这个例子中,* 表示提交,| 表示分支,| 的数量表示分支的层级,而 ( 和 ) 则用来表示分支的指向和合并点。从输出中我们可以看到 feature1 分支在 master 分支上创建了一个新的提交 14096d0,并且最终被合并到了 master 分支上,形成了一个合并提交 cf810e4
现在,既然我们已经完成了合并并且解决了所有的冲突,我们可以安全地删除 feature1 分支了。使用 git branch -d 命令并跟上分支名称可以删除一个分支:

5.3 分支管理策略

在Git中,当合并一个分支到另一个分支时,如果合并可以简单地通过移动分支指针来完成(即不需要复杂的合并操作),Git通常会选择“快进”模式(Fast-Forward)。然而,这种模式下,一旦删除了被合并的分支,相关的合并历史信息就会丢失。
如果你希望在合并时保留这些信息,即使合并可以通过快进完成,你也可以使用--no-ff选项来强制Git创建一个新的合并提交。这样做的好处是,从分支历史中可以清晰地看出哪些提交是合并的结果,即使这些合并在逻辑上是简单的。
以下是如何使用--no-ff选项进行合并的步骤:
首先,创建一个新的分支并切换到这个分支上:
在这个分支上,修改一些文件并提交:
然后,你切换回主分支(通常是mastermain):
准备合并dev分支到master分支,并使用--no-ff参数来禁用快进合并,-m参数允许你为合并提交添加一条消息,以描述这次合并的内容:
最后,你可以使用git log命令来查看分支历史,并确认合并提交已经创建:
在上面的日志输出中,你可以看到e1e9c68是一个合并提交,它有两个父提交:一个是master分支上的最后一个提交,另一个是dev分支上的提交f52c633。这表明即使合并可以通过快进完成,但由于使用了--no-ff选项,Git还是创建了一个新的合并提交。
notion image
这样做的好处是,即使在将来删除了dev分支,你仍然可以通过查看master分支的合并提交来知道哪些更改是在dev分支上进行的。这对于代码审查和跟踪项目的演变历史非常有用。
在实际软件开发中,分支管理应遵循几个核心原则以确保代码质量和项目流程的顺畅。首要原则是保持master分支的稳定性。master分支通常被视为项目的“主线”,只用于发布稳定且经过充分测试的版本。这意味着开发者不应直接在master分支上进行日常的开发工作。
实际开发工作通常在另一个分支,如dev(开发)分支上进行。dev分支作为不稳定分支,用于集成新功能、修复bug以及进行其他开发活动。当开发到一定程度,比如准备发布1.0版本时,开发者会将dev分支合并到master分支,并在master分支上发布该版本。
在团队协作的环境中,每个开发者通常会在自己的个人分支上工作。这些个人分支从dev分支创建,开发者在其中实现特定的功能或修复bug。当功能完成并通过测试后,个人分支会被合并回dev分支,从而确保dev分支始终包含最新的、经过验证的代码更改。所以,团队合作的分支看起来就像这样:
notion image

5.4 Bug分支

在软件开发过程中,bug修复是常见的任务。当你需要在Git中修复一个bug,但当前分支(例如dev)上还有未完成的工作时,你可以使用Git的stash功能来临时保存你的工作进度,然后切换到其他分支(如master)进行bug修复。
还是沿用之前的例子,首先,使用git stash保存当前分支dev的工作进度:
这会创建一个stash,你可以在git stash list中看到它。现在你的工作区是干净的,可以安全地创建一个新的分支来修复bug。假设你需要在master分支上修复bug,你可以这样做:
在新分支issue-101上修复bug,例如修改readme.txt文件,然后提交更改:
修复完成后,切换回master分支并合并issue-101分支:
现在,你可以删除issue-101分支:
最后,你可以切换回dev分支并恢复之前保存的工作进度。使用git stash pop来恢复并删除stash:
现在,你可以继续在dev分支上进行你的工作。如果你需要查看所有的stash,可以使用git stash list命令。如果你想要恢复特定的stash,可以使用git stash apply命令,但记得使用git stash drop来清理不再需要的stash。
当在master分支上修复了一个bug后,通常这个bug也会存在于dev分支上,因为dev分支是从master分支衍生出来的。为了在dev分支上也修复这个bug,你不需要重复整个修复过程,Git提供了一个非常方便的命令cherry-pick,它允许你将一个特定的提交(在这个例子中是修复bug的提交)应用到当前分支。
首先,你需要确定dev分支上没有未提交的更改,如果有,你需要先提交或者stash它们。然后,你可以使用git cherry-pick命令来复制master分支上的修复提交到dev分支。这里是如何操作的:
确保dev分支是当前分支:
使用git cherry-pick命令来应用master分支上的修复提交:
这里4c805e2是修复bug提交的哈希值。执行这个命令后,Git会将这个提交的更改应用到dev分支上,并创建一个新的提交。这个新的提交会有一个不同的哈希值,因为它是在不同的分支上创建的。
如果cherry-pick过程中没有冲突,Git会自动创建一个新的提交。如果有冲突,你需要手动解决这些冲突,然后继续提交过程。
这样,你就在dev分支上“重放了”master分支上的修复,而不需要重复修复bug的过程。这种方法不仅节省时间,而且保持了代码的一致性。如果你在dev分支上修复了bug,然后想要在master分支上应用这个修复,你同样可以使用git cherry-pick命令,但在此之前,你可能需要使用git stash来保存dev分支上的当前工作进度。

5.5 feature分支

在软件开发过程中,新功能的需求源源不断,每个新功能的添加都需要精细的管理,以保持代码库的整洁和稳定。为了确保主分支(通常是开发或主线分支)不被实验性代码干扰,当添加新功能时,开发者通常会遵循一种分支策略:为每个新功能创建一个独立的特性分支(feature branch)。在这个分支上完成开发后,代码会被合并回主分支,随后该特性分支会被删除。
比如,你接到了一项新任务:开发一个名为Vulcan的新功能,它将用于下一代星际飞船。你开始了新功能的开发流程:
经过五分钟的快速开发,你完成了Vulcan功能的代码编写。你添加了新文件vulcan.py到暂存区,并检查了状态:
然后,你提交了这次更改:
接下来,你计划切换到主分支dev来准备合并新功能:
通常,feature分支和bug分支的处理流程类似:开发完成后,你会将它们合并回主分支,然后删除这些分支。
然而,在这个关键时刻,你收到了一个不幸的消息:由于经费问题,Vulcan功能被取消了。虽然你的工作成果因此变得不再需要,但由于这个分支包含了机密信息,你必须立即销毁它。
你尝试使用以下命令删除feature-vulcan分支:
Git警告你,feature-vulcan分支还没有被合并,直接删除会丢失修改。如果你确定要删除它,可以使用大写的-D参数。
现在我们强行删除这个分支:

5.6 多人协作

当你从远程仓库克隆一个代码库时,Git 会自动将本地的 master 分支与远程的 master 分支进行关联,并将远程仓库的默认名称设置为 origin。这样,你就可以轻松地与远程仓库进行交互操作。
要查看已配置的远程仓库信息,你可以使用 git remote 命令。它会显示所有已配置的远程仓库的名称。例如:
使用 git remote -v 命令可以显示更详细的信息,包括远程仓库的URL和用于推送(push)和抓取(fetch)的别名。例如:
如果你需要将本地的分支更改推送到远程仓库,你可以使用 git push 命令,并指定远程仓库的名称和要推送的分支。例如,要将 master 分支推送到 origin 远程仓库,你可以执行:
同样地,如果你想推送其他分支,比如 dev,你需要相应地更改命令:
关于推送哪些分支到远程仓库,这通常取决于你的工作流程和团队约定。以下是一些常见的策略:
  • master分支:作为主分支,通常应该时刻保持与远程仓库的同步,因为它代表了项目的稳定版本。
  • 开发分支(如 dev):这样的分支用于日常开发活动,通常也需要与远程同步,以便团队成员可以共享和协作开发。
  • bug分支:如果bug修复仅需要在本地完成,则可能不需要推送到远程仓库。但是,如果你需要与他人协作或在多个环境中测试修复,则可能需要推送。
  • 特性分支(如 feature/new-feature):是否推送这些分支取决于你是否需要与团队成员在这些特性上协作。
总的来说,在Git中,你可以根据需要选择推送哪些分支到远程仓库。这完全取决于你的工作流程、项目需求和团队协作的方式。
抓取分支
在多人协作的项目中,团队成员会经常向 master 和 dev 这样的主要分支推送他们的更改。为了模拟这种协作环境,假设你的一个团队成员(我们可以称他为“小伙伴”)在另一台电脑上(当然,他需要确保已经将他的SSH密钥添加到GitHub账户中)或在同一台电脑的另一个文件夹中克隆了仓库。
他会执行如下命令来克隆仓库:
克隆完成后,小伙伴在他的本地仓库中默认只能看到 master 分支。他可以使用 git branch 命令来验证这一点:
如果小伙伴想要在 dev 分支上进行开发,他首先需要确保本地有这个分支的最新副本。由于 dev 分支在克隆时不会自动创建到本地,他可以使用下面的命令来创建并切换到 dev 分支,同时与远程的 origin/dev 分支建立关联:
现在,小伙伴可以在 dev 分支上开始他的工作,并且可以将他的更改定期推送到远程仓库。例如,他添加一个新文件 env.txt,提交这个更改,并将其推送到远程 dev 分支:
请注意,在上述的 git push 命令中,添加了 -u 标志,这样在未来的推送和拉取操作中,Git 会知道 dev 分支应该与远程的 origin/dev 分支保持同步。现在,每次小伙伴在本地 dev 分支上做了更改并想要与远程仓库同步时,他只需要简单地执行 git push 或 git pull 命令即可。
当你的小伙伴已经将他的提交推送到了 origin/dev 分支,而你在本地对同一个文件进行了修改并尝试推送时,你可能会遇到冲突。这是因为 Git 不允许你覆盖远程仓库中的更改,除非你明确地进行了合并或拉取操作。
为了解决这个问题,你需要首先拉取(pull)远程仓库的最新更改,并将它们合并到你的本地分支中。但是,由于你的本地分支之前没有设置跟踪远程分支,所以你需要先设置这个跟踪关系。
你可以使用以下命令来设置跟踪关系并拉取远程更改:
在拉取过程中,Git 检测到冲突并停止了自动合并。你需要手动解决这个冲突。冲突通常会在有冲突的文件中以类似下面的形式标记出来:
你需要决定保留哪些更改,删除 Git 添加的标记,然后保存文件。解决冲突后,你需要将解决冲突的文件添加到暂存区,并提交这个解决冲突的更改:
现在,你的本地分支包含了与远程分支的合并,并且没有冲突。你可以再次尝试推送你的更改:
这样,你的更改就被成功地推送到了远程仓库的 dev 分支。在未来的协作中,定期拉取和推送更改是保持代码同步的重要步骤。
在多人协作的工作模式中,通常遵循以下步骤来推送和更新代码:
  1. 推送本地修改:首先,你会尝试使用 git push origin <branch-name> 命令将本地的修改推送到远程仓库的对应分支。
  1. 处理推送失败:如果推送失败,这通常意味着远程分支比你的本地分支更新,包含了一些你没有的提交。此时,你需要先拉取远程仓库的最新更改。
  1. 拉取远程更新:使用 git pull 命令来拉取远程仓库的更新。如果提示“no tracking information”,意味着本地分支尚未与远程分支建立跟踪关系。此时,你需要使用 git branch --set-upstream-to <branch-name> origin/<branch-name> 命令来设置跟踪关系。
  1. 解决合并冲突:在拉取远程更新后,如果本地和远程的更改存在冲突,Git 会停止合并过程。你需要手动编辑冲突文件,解决冲突,并在解决后使用 git add 命令将文件标记为解决状态。
  1. 提交解决后的更改:解决冲突后,使用 git commit 命令提交这些更改,描述解决冲突的内容。
  1. 再次推送:完成上述步骤后,再次使用 git push origin <branch-name> 命令将本地分支的更改推送到远程仓库。
熟悉并掌握这一工作流程后,多人协作将变得更加高效和顺畅。

5.7 Rebase

在多人协作的项目中,当多个开发者在同一个分支上工作时,很容易遇到合并冲突。即使没有冲突,后推送的开发者也需要先拉取(pull)远程分支的更新,然后在本地合并,最后才能成功推送(push)自己的更改。这样的操作会导致分支的提交历史变得复杂,出现多个合并提交。
某些强迫症同学总对此感到心痒,为了解决这个问题,Git提供了rebase命令,它可以将本地分支的提交“重新排序”,使得提交历史变得更加线性和清晰。下面是使用rebase的一个例子:
首先,确保你的本地分支是最新的。如果你的本地分支比远程分支落后,你需要先拉取远程分支的更新:
这会合并远程分支的更改到你的本地分支,并解决任何冲突。
然后,你可以使用git rebase来整理你的提交历史。假设你想要将本地分支的提交重新基于远程分支的最新提交:
这个命令会将你的本地分支的提交“挪动”到远程分支的最新提交之后。在这个过程中,Git会尝试逐个应用你的本地提交,如果有冲突,你需要手动解决。
一旦rebase完成,你的本地分支的提交历史将变得线性,所有的提交都会基于远程分支的最新提交。这时,你可以使用git push来推送你的本地分支到远程仓库:
由于你的本地分支的提交已经重新排序,远程分支的提交历史也会相应地更新,变得更加线性。
需要注意的是,rebase会改变本地分支的提交历史,这可能会对其他协作者造成困扰,特别是当他们的本地分支包含了你的提交时。因此,在使用rebase之前,最好确保没有其他人基于你的本地分支工作,或者在团队中达成一致的理解。如果你需要与其他人共享你的分支,通常推荐使用merge而不是rebase

六、标签管理

在软件开发中,当我们准备发布一个新版本时,通常会在版本控制系统中创建一个标签(tag)。这个标签代表了特定时刻的代码快照,它指向一个特定的提交(commit),从而唯一确定了那个时刻的版本状态。这样,无论何时,只要提到这个标签,我们就能迅速找到并获取到对应的版本。
Git中的标签实际上是一种轻量级的指针,它们与分支类似,都是指向特定提交的引用。但与分支不同的是,标签一旦创建,就不会再移动。这意味着标签是不可变的,它们始终指向创建时所指向的提交。创建和删除标签的操作都是即时完成的,不会影响其他分支或标签。
那么,为什么在已经有了提交(commit)的情况下,我们还需要引入标签(tag)呢?原因在于,提交的标识通常是一串复杂的哈希值,这些值对于人类来说难以记忆和识别。而标签则提供了一种更加友好、易于理解和记忆的方式来引用特定的版本。例如,与其告诉团队成员“请找到哈希值为6a5819e的提交来打包发布”,不如简单地说“请根据标签v1.2来打包发布”。这样,团队成员可以快速地通过标签名找到对应的版本,而不需要去记忆或查找复杂的提交哈希值。
标签在Git中扮演了一个重要的角色,它们为开发者提供了一种清晰、易于管理的方式来标记和引用项目的不同版本。

6.1 创建标签

在Git中,打标签是一个简单的操作,用于标记重要的提交点。首先,你需要确定你想要在哪一个分支上打标签,然后切换到该分支。
接下来,你可以使用git tag命令后跟标签名称来创建一个新的标签。默认情况下,这个标签会打在当前的最新提交上。
要查看所有已创建的标签,可以使用git tag命令。
然而,如果你忘记在某个特定的提交上打标签,你可以通过找到该提交的commit ID来手动打上标签。首先,使用git log命令来查看提交历史并找到你想要打标签的提交的commit ID。
假设你想要对add merge这次提交打标签,其对应的commit ID是f52c633。你可以通过以下命令来创建标签:
再次运行git tag命令,你将看到新创建的标签v0.9和之前的标签v1.0
需要注意的是,Git中的标签不是按照时间顺序列出的,而是按照字母顺序排序的。你可以使用git show <tagname>命令来查看标签的详细信息。
此外,Git还支持创建带有说明的标签。你可以使用-a选项来指定标签名,并使用-m选项来提供说明文字。
使用git show <tagname>命令可以查看带有说明的标签的详细信息。
需要注意的是,标签总是与特定的提交相关联。如果一个提交出现在多个分支中,那么这些分支都可以看到这个标签。

6.2 操作标签

如果标签打错了,不用担心,Git 允许你删除它。要删除一个本地标签,你可以使用 git tag -d 命令 followed by the tag name. 例如,要删除名为 v0.1 的标签,你可以执行:
请注意,删除的标签只会在本地仓库中移除,不会自动从远程仓库中删除。这意味着其他开发者仍然可以在他们的本地仓库中看到这个标签,除非他们也执行了删除操作。
如果你想要将某个标签推送到远程仓库,你可以使用 git push 命令 followed by origin and the tag name. 例如:
如果你想推送所有尚未推送到远程的本地标签,你可以使用 --tags 选项:
然而,如果标签已经被推送到远程仓库,并且你想要删除它,你需要先从本地删除它,然后再从远程删除。删除远程标签的命令与推送标签的格式类似,但你需要使用冒号 : 前缀和 refs/tags/ 前缀来指定要删除的远程标签。例如:
在执行这些操作后,为了确认标签是否已经从远程仓库中删除,你可以登录到 GitHub 并检查仓库的标签页面。如果一切正常,你应该不再看到已删除的标签。

七、Git个性化

7.1 自定义Git

在安装Git一节中,我们已完成了user.name和user.email的基本配置。但实际上,Git提供了众多可配置项,以满足不同用户的需求和偏好。
为了使Git的输出更加醒目和易于阅读,你可以配置Git以显示颜色。通过在命令行中执行以下命令,可以启用颜色显示功能:
启用后,Git会在适当的时候显示不同的颜色,比如在执行git status命令时,各种状态信息将以不同的颜色展示,从而更加直观地呈现仓库的当前状态。这样的配置不仅提升了用户体验,还有助于快速识别和理解Git的输出信息。
notion image
我们在后面还会介绍如何更好地配置Git,以便让你的工作更高效。

7.2 忽略特殊文件

在使用Git进行版本控制时,我们经常会遇到一些不希望提交到版本库的文件,比如包含敏感信息的配置文件或者操作系统自动生成的临时文件。为了管理这些文件,Git提供了.gitignore文件,它允许我们定义一系列规则来告诉Git哪些文件应该被忽略。
不需要从头写.gitignore文件,GitHub已经为我们准备了各种配置文件,只需要组合一下就可以使用了。所有配置文件可以直接在线浏览:https://github.com/github/gitignore
忽略文件的原则是:
  1. 忽略操作系统自动生成的文件,比如缩略图等;
  1. 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;
  1. 忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。
首先,创建.gitignore文件并添加规则是很简单的。你可以在Git工作目录的根目录下创建这个文件,并在其中列出你想要忽略的文件或文件模式。例如,如果你在Windows环境下进行Python开发,你可能需要忽略以下文件:
同时,你可能还有一些个人配置文件,如数据库密码文件,也需要添加到.gitignore中:
完成这些规则后,你应该将.gitignore文件提交到Git,这样所有协作者都能遵循相同的忽略规则。
需要注意的是,.gitignore文件本身应该被Git跟踪,因为它是一个项目级别的配置文件。正如在开头提到的,GitHub提供了各种预设的.gitignore模板,你可以直接使用或组合这些模板来创建适合你项目的.gitignore文件。
如果你发现某个文件被.gitignore忽略了,但你确实需要将其添加到版本库,你可以使用git add -f命令来强制添加该文件。如果你不确定是哪个规则导致了忽略,可以使用git check-ignore命令来检查:
此外,如果你的.gitignore规则过于宽泛,导致一些你实际上想要跟踪的文件也被忽略了,你可以在.gitignore中添加例外规则。例如,如果你想要跟踪.gitignore文件和App.class文件,你可以这样写:
通过在文件名前加上!,你可以告诉Git忽略.gitignore中的某些规则。这样,你就可以在不破坏.gitignore规则的前提下,添加特定的文件到版本库。

7.3 配置别名

在使用Git时,我们有时会敲错命令,或者觉得某些命令太长不好记。为了解决这个问题,Git允许我们为常用命令创建别名(alias),这样我们就可以输入简短的命令来执行复杂的操作。
例如,如果你经常忘记git status命令,你可以创建一个别名st来代替它:
这样,你就可以通过输入git st来快速查看当前分支的状态。
同样地,你可以为其他常用命令设置别名,比如:
这样,你就可以使用git co来切换分支,git ci来提交更改,git br来查看分支列表。
如果你想要撤销对某个文件的暂存(unstage),你可以创建一个unstage别名:
然后,当你输入git unstage test.py时,Git实际上执行的是git reset HEAD test.py
为了快速查看最后一次提交,你可以创建一个last别名:
使用git last命令,你将看到最近一次的提交信息。
甚至,你可以创建一个非常详细的日志别名,例如:
这样,当你输入git lg时,Git将显示一个包含颜色、图形和格式化信息的详细日志。
为什么不早点告诉我?别激动,咱不是为了多记几个英文单词嘛!
配置文件
在配置Git时,如果你使用--global选项,那么配置将应用于当前用户在所有仓库中的操作。如果不使用--global选项,配置则只针对当前仓库有效。
Git配置文件的位置取决于你是在为特定仓库配置还是为当前用户全局配置。对于特定仓库的配置,文件通常位于该仓库的.git目录下,名为config。例如:
在这个配置文件中,你可以找到别名(alias)的设置。要删除一个别名,只需从[alias]部分中删除对应的行。
对于全局配置,文件通常位于用户的主目录下,名为.gitconfig。这个文件是一个隐藏文件,所以你可能需要使用cat命令来查看它:
配置别名也可以直接修改这个文件,如果改错了,可以删掉文件重新通过命令配置。

7.4 搭建Git服务器

在远程仓库的讨论中,我们了解到远程仓库的本质与本地仓库相同,其主要作用是作为一个中心点,以便团队成员可以全天候地交换代码更改。
GitHub作为一个流行的远程仓库托管服务,为开源项目提供免费的代码托管。然而,对于那些需要保护源代码不被公开的商业公司来说,他们可能会选择自建Git服务器,以创建私有仓库。
要搭建自己的Git服务器,你需要一台运行Linux的服务器,推荐使用Ubuntu或Debian系统,因为它们可以通过简单的apt命令来安装所需的软件。
以下是搭建Git服务器的基本步骤:
1.安装Git:
2.创建一个专门用于Git服务的用户(例如git):
3.设置证书登录,收集团队成员的公钥(id_rsa.pub文件),并将它们添加到/home/git/.ssh/authorized_keys文件中,每个公钥一行。
4.初始化Git仓库。选择一个目录作为仓库位置(例如/srv/sample.git),然后运行:
这将创建一个裸仓库,它没有工作目录,因为服务器上的仓库主要用于共享。然后,更改仓库的所有者为git用户:
5.禁用git用户的shell登录。出于安全考虑,编辑/etc/passwd文件,将git用户的登录shell更改为/usr/bin/git-shell,这样git用户只能通过SSH使用Git,而无法登录到服务器的shell。
6.克隆远程仓库。现在,团队成员可以在各自的电脑上使用git clone命令来克隆远程仓库:
对于公钥的管理,如果团队规模较小,手动收集和添加公钥是可行的。但对于大型团队,可能需要使用Gitosis等工具来自动化公钥管理。
搭建私有Git服务器为团队提供了一个安全的环境来协作和共享代码,同时保持代码的私密性。

八、写在最后

终于把这篇教程更完了,首先恭喜学到这里的你,现在已经基本具备使用Git进行开放的能力了,应付小体量的项目是完全足够的,以后只会越用越顺手。虽然从开坑到结束只有一星期时间,实际这是更早些时候用更多时间写出来的,只不过一直放在其他地方,由于最近加班比较严重,不能每天进行补充,完成时间还是比预期长的。最近工作还是很忙,但还是打算再开个(些?)新坑,记录自己的学习过程,应该很快就要新建文件夹了,祝愿自己不会鸽太久吧😆。
 
 
💡
有关Git使用上的问题,欢迎您在底部评论区留言,一起交流~
 
组合优化与凸优化利用CF加速游戏网络
Loading...
LBenedict
LBenedict
一个普通的干饭人🍚
最新发布
组合优化与凸优化
2024-5-11
Git入门:从Push到Pull(已完结)
2024-3-6
利用CF加速游戏网络
2024-2-6
博客站建立
2023-12-20
公告
🎉博客站正式建立🎉
--- 随缘更新 ---
-- 2023.12.20 --
催更小技巧:常来看看