..

Git 基础知识

Intro

什么是 版本控制

用学术化的语言来表述,版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。

在没有版本控制软件之前,我们如果要控制文件的版本,往往会复制整个项目目录的方式来保存,然后再在项目文件的后面加上简单的版本号,例如 _1、_2 、_3 ,有时候简单的版本号也不够用,我们可能还会在后面加上其他的标记符,例如 _1_Test 、_1_20250401 …… 诸如此类。

这么做唯一的好处就是简单,但是特别容易犯错。 有时候会混淆所在的工作目录,一不小心会写错文件或者覆盖意想外的文件。

为了解决这个问题,上个世纪1990年就有了第一个版本管理软件,叫做 CVS (Concurrent Versions System)。

当然,版本控制软件的历史不是重点,我们这里只是简要概括,有兴趣的可以翻阅维基百科的版本控制词条。

什么是 Git

Git 是一个用于进行版本控制的软件,使用 Git 进行版本控制既可以用图形化的方式,也可以用命令行的方式。

相较于复制文件夹进行版本管理,Git 可以在同一个文件夹下对软件进行版本管理。

由于 Git 是开源的,格式规范很容易获取,许多的个人开发者和公司都做过 Git 相关的 GUI 方案。例如 Github 的 Github Desktop,Tencent 的 UGit,Atlassian 的 SourceTree,Dan Pristupov 和 Tanya Pristupova 做的 Fork。

无论是使用的哪种软件,只要格式是 Git 格式就能互相兼容,具体使用哪个软件纯看个人喜好。

Git 基础

在一个创建好的 Git 仓库,Git 有三个主要的工作区域。

  • 工作区(Working Directory):就是你实际看到的项目文件目录
  • 暂存区(Staging Area):一个中间区域,里面存放准备提交的内容
  • 版本库(Repository):存储项目的所有版本历史

常见的工作流程如下:

  1. 修改项目文件目录中的内容 → 修改工作区
  2. 想尝试一些其他方案,但希望保留当前的修改 → 将工作区的修改添加到暂存区
  3. 新方案更好,并且希望保留 → 将工作区的修改添加到暂存区,覆盖之前暂存的内容
  4. 当前小需求完成,提交内容 → 将暂存区的内容添加到版本库

Git 仓库的创建与删除

git 的仓库创建很容易,直接在所在文件夹使用 git init 命令即可。

git 没有提供删除仓库的方案,不过可以直接进入文件夹找到隐藏的 .git/ 文件夹将其删除来实现移除 git 管理。

Git 添加更改

git addgit commit

Git 分支管理

git branchgit checkoutgit switchgit mergegit rebase

Git 撤回更改

git resetgit revertgit restore 都是撤销修改/提交的命令。

git reset

  • git reset --soft <commit>
    • 只移动 HEAD 到指定 commit
    • 保留暂存区和工作区的修改
    • 常用场景:撤销最近一次 commit,但保留代码和 add 状态
  • git reset --mixed <commit>(默认)
    • 移动 HEAD,并清空暂存区
    • 工作区的文件还在,但需要重新 git add
    • 常用场景:取消 add,重新选择要提交的文件
  • git reset --hard <commit>
    • 移动 HEAD,并清空暂存区和工作区
    • 本地修改全部丢弃
    • 常用场景:彻底回退,不要任何更改

git revert

用于创建一个“新的 commit”,用来“抵消”某个提交的内容。

revert 不会删除历史,历史是可追溯的。可以安全用于公共分支(例如 main、master)。

这会生成一个新的提交,内容和 <commit> 的反操作相同。适合在多人协作时回滚线上错误,而不破坏历史记录。

git restore

如果只需要在当前的工作区撤回更改,或者复原某几个文件的历史记录,而不是操作整个 commit,可以使用 git restore 命令。

 1# 撤回工作区更改
 2git restore .
 3
 4# 撤回某个文件的更改
 5git restore file_name.md
 6
 7# 撤销暂存区所有已暂存的更改
 8# 等价于旧命令 git reset .
 9git restore --staged .
10
11# 撤回暂存区某个文件的更改
12git restore --staged file_name.md

Git 团队协作

Git 仓库管理

添加仓库:git remote add remote_name url

查看仓库:git remote -v

修改仓库地址:git remote set-url remote_name new_url

删除远程仓库:git remote remove remote_name

Git 获取和推送更改

从服务器获取更改但不合并:git fetch

从服务器获取更改并合并:git pull

从服务器拉取指定分支的更改:git pull remote_name branch_name

git fetchgit pull 的区别

git fetch 只把远程仓库的新提交下载到本地,但 不会修改当前分支。下载的内容存放在 远程跟踪分支(如 origin/main)中。需要本地手动 merge 或 rebase 才能更新本地分支。这样做的好处是可以先看看远程更新的内容,再决定是否合并。

使用 fetch 可以安全地查看远程变化,不影响当前代码。团队里开发时,一般先 fetch,再决定如何合并。

git pull = git fetch + git merge ,拉取远程分支并自动尝试合并到当前分支。如果本地和远程有冲突,就会进入冲突解决流程。

使用 pull 的场合是确认就是要把远程改动合并进来,不需要中间检查。在开发中同步团队最新代码时常用。

git pullgit pull remote_name branch_name 的区别

git pull 会默认使用你当前所在分支跟踪的远程分支,并将其更改远程更改的内容拉取到到你的本地,再将更改合并到你当前分支中。

git pull 则是将指定的远程分支的内容合并到指定的本地分支中。

git push 中的 -u 是什么意思?

在第一次 push 分支的内容到服务器上时,需要使用 git push remote_name branch_name 这样的命令,他可以将分支的内容推送到远程服务器上。

但我们会注意到,一些网络上的写法会在这个命令的基础上添加一个关键字 -u ,会告诉你这样就能够实现当前分支与远程分支的一个自动跟踪。

这里做一个详细的说明,-u--set-upstream 的缩写,意思是「设置上游分支」,功能是将本地分支和远程分支关联起来。

当你执行 git push -u remote_name branch_name 时,Git 会把本地的 branch_name 分支推送到远程的 remote_name 仓库,并在本地记录一个“上游分支”的设置。 这样以后之后只需要用 git push 或 git pull,不用再指定远程和分支名,Git 会自动知道推送/拉取到哪个远程分支。

Git 分支与 Diff 算法 与 二进制文件

由于 Git 创建分支几乎是零成本,因此 Git 开发流程中往往会鼓励开发者多创建分支,多合并分支,而不是一直在某个分支上进行开发。

Git 通过 Diff 算法,可以保证两份数据合并的时候不会出问题。

但是,算法本身只能对文本文件进行差分,对于二进制文件只能全量保存,因此游戏模型、游戏音频、Word、Excel、Zip、Rar 等二进制文件都非常容易导致 Git 冲突,也很容易导致 Git 仓库的体积不受控制的增大。

通常的解决方案是使用 Git LFS 对大文件进行单独的管理。

Git Flow 分支模型

为了充分利用 git 分支的特性,并规范产品开发流程,开发人员根据实践经验总结出 Git Flow 工作流。它是最早诞生、也是得到最广泛采用的一种工作流程。

  • main 分支:只接收 release 和 hotfix 合并
  • develop 分支:持续开发,接收 feature、release、hotfix 的合并
  • feature:临时分支,开发完就合并回 develop
  • release:准备上线版本,用来测试和修复
  • hotfix:线上紧急修复,直接基于 main
gitGraph
   commit id: "初始提交"
   branch develop
   commit id: "开发开始"

   branch feature/awesome-feature
   commit id: "功能开发中"
   commit id: "功能完成"
   checkout develop
   merge feature/awesome-feature tag: "合并功能"

   branch release/1.0
   commit id: "准备发布"
   checkout main
   merge release/1.0 tag: "发布 v1.0"
   checkout develop
   merge release/1.0

   branch hotfix/1.0.1
   commit id: "紧急修复"
   checkout main
   merge hotfix/1.0.1 tag: "发布 v1.0.1"
   checkout develop
   merge hotfix/1.0.1

上图为 Git Flow 的完整流程,看着很复杂,但其实是可以将分支简化为两部分。

长期分支:主分支 和 开发分支

前者用于存放对外发布的版本,任何时候在这个分支拿到的都应该是稳定无Bug的内容。

后者用于日常开发,存放最新的版本。

短期分支:功能分支 和 补丁分支

功能分支(有时也称为主题分支)用于为即将发布或更远的未来版本开发新功能。

在开始开发某个功能时,该功能最终将包含在哪个目标版本中可能尚不清楚。功能分支的目的在于,只要该功能仍在开发中,它就会一直存在,但最终会被合并回开发分支(以确保将新功能添加到即将发布的版本中)或被丢弃(以防实验结果令人失望)。

补丁分支的出现是因为需要立即处理当前生产版本中出现的非预期状态。当生产版本中存在严重错误需要立即解决时,可以从标记生产版本的主分支上相应的标签分支出一个热修复分支。

在将问题修复后,将该分支的内容分别合入主分支和开发分支,保证错误被正常修复。

Reference

UGit - 让每个人都可以轻松使用Git

Git

Git快速入门

【⭐核心】Git完整教程:从入门到分支模型与最佳实践_哔哩哔哩_bilibili

Git工作流面面观——Gitflow工作流

Git 工作流程 - 阮一峰的网络日志

A successful Git branching model

Git分支管理策略 - 阮一峰的网络日志

The Myers Difference Algorithm

Commit message 和 Change log 编写指南 - 阮一峰的网络日志

There is nothing new under the sun.