Skip to content

今天你 commit 了么?

🧸 代码提交的乐子

提交的类型标签可以参考 gitmoji,常用的如下:

commit 模板说明Explain
🎉 initial:新开项目,初次提交Start an adventure
✨ feat:引入新特性/功能Introduce new features
🍻 drunk:醉写代码
代码没写完
Write code drunkenly
🐛 fix:修复bugFix a bug
🚑️ hotfix:重要补丁Critical hotfix
📦 chore:构建过程或辅助工具的变动Other modifications
🚚 chore:移动或重命名文件move/rename file
🎨 style:代码格式修改Format
📝 docs:修改文档Add or update documentation
🛠 build:影响项目构建或依赖项修改
🏗️ architect:架构的变动Make architectural changes
🚀 perf:优化程序性能Improve performance
🔨 refactor:代码重构Refactor
🔬 test:测试用例新增、修改Add, update, or pass tests
⬆️ dependencies:更新依赖版本Upgrade dependencies
⬇️ dependencies:降低依赖版本Downgrade dependencies
📌 dependencies:固定依赖版本Pin dependencies to specific versions
✅ release:发布新版本
🔄 workflow:工作流相关文件修改
🔀 merge:合并分支Merge branches
⏪️ revert:恢复更改Revert changes
⏳ revert:恢复上一次提交
🔖 tag:发布版本Release / Version tags
🙈 gitignore:添加或更新 .gitignoreAdd or update .gitignore
👷 ci/cd:添加 CI 构建系统 / 修复 CI 构建问题Fix CI Build
🔒 safety:修复安全问题
🐧 linux:修复 Linux 下的问题Fix Linux issues
🍎 macos:修复 macOS 下的问题Fix macOS issues
🍏 ios:修复 iOS 下的问题Fix iOS issues
🤖 android:修复 Android 下的问题Fix Android issues
🚨 lint:移除 linter 警告Remove linter warnings
🚧工作进行中Working
🐳 docker:Docker 相关工作Docker
🌐 i18n:国际化相关Internationalization

某知乎网友:“这不是有没有必要,而是有没有意思。如果觉得给我的commit添加一个表情有意思的话,花点时间看看文档又有什么关系呢,又能浪费几分钟时间呢,大不了少花点时间看手机嘛

提交的时候,按照如下格式

bash
<type>(<scope>): <subject>
// 空一行
<body>
// 空一行
<footer>
  • type 是必须的,是关于本次提交的类型标签
  • scope 用于说明 commit 影响的范围
  • subject 是必须的, commit 的简短描述,要求:
    • 义动词开头,使用第一人称现在时(change,而不是changed)
    • 第一个字母小写
    • 结尾不加句号(.)
  • body 是对 commit 的详细描述,要求:
    • 使用第一人称现在时

🔰 Git 的一些概念

Git是一个分布式版本控制系统,它可以帮助你跟踪代码的变化,并与其他开发者协作。

版本控制是一种管理文件历史记录的方法,它可以让你回溯到任何一个时间点的文件状态,比较不同版本之间的差异,以及合并多人的修改。版本控制对于软件开发非常重要,因为它可以帮助你保持代码的完整性和一致性,避免错误和冲突,以及提高团队协作效率。

git仓库

  • 工作区 (Workspace):写代码的地方,在编辑器(vscode)写的每一行代码都是在工作区进行的
  • 暂存区 (Index/Stage):对工作区的代码进行暂存并等待提交,进入了暂存区才会真正进入版本控制阶段(.git/index 中保存了从工作区暂存的更改)
  • 本地仓库 (Repository):提交后的代码在本地仓库中进行版本控制
  • 远程仓库 (Remote):云端仓库,一般存在 Github 上

🔨 Git 配置

配置级别

配置文件有三个级别,其优先级为:local > global > system

配置级别配置文件说明
--local.git/config仅对当前仓库有效
--global~/.gitconfig对当前用户有效
--system/etc/gitconfig对系统所有用户有效

用户配置

开始之前,需要对git进行一些配置,关键是配置用户名和邮箱,这是用于与远端仓库进行联系的钥匙。

配置用户名 user.name 和邮箱 user.email

bash
# 配置写入 ~/.gitconfig 对当前用户生效
# ✅ 推荐个人电脑上使用
git config --global user.name  "your_name"
git config --global user.email "your_email@gmail.com"
bash
# 配置写入 .git/config 对当前仓库生效
# 需要进入某个 Git 仓库中才能让该选项生效
git config --local user.name  "your_name"
git config --local user.email "your_email@gmail.com"
bash
# 配置写入 $(prefix)/etc/gitconfig 对所有用户生效
# ❌ 非常不推荐这种写法
git config --system user.name  "your_name"
git config --system user.email "your_email@gmail.com"

查看全部配置,也可以查看特定的配置:

bash
git config --list
git config user.name

默认编辑器

  • 需要提前说明的是,git 的操作分为两种,一种是命令行进行操作 (例如 git rebase --interactive/-i ),另一种是在编辑器 (例如 vscode/Jetbrains) 中进行操作,使用命令行的方式是明确自己在做什么,熟悉之后再使用编辑器进行操作,可以提高效率。
  • 在 vscode 中的 pull (拉取) 操作,默认就是采用 rebase 的方式,而用户有时候是希望采用 merge 的方式,因此需要在 vscode 中进行配置,因此在使用编辑器之前请确保了解每一次点击的操作具体在做什么
  • Jetbrains 的 git 会比较好用,但是收费;所以我选 vscode + 命令行

配置默认编辑器 为 VScode

shell
git config --global core.editor code  # 全局 VScode
# git config --global core.editor vim / nano / $(which code)

查看当前设置的编辑器

shell
git config --get core.editor
git config --get --global core.editor # 获取全局

🔨 Git 基本操作

开始一个新项目

在开一个新项目时候,需要初始化一下,并且进行一次提交

bash
git init        # 初始化仓库,生成 .git 目录
touch README.md # 给项目新建一个说明文件吧
git add .       # (或者)添加文件到暂存区。
git commit      # 将暂存区内容添加到仓库中。
git commit -m "🎉 initial: Start an adventure" # (或者)将暂存区内容添加到仓库中,并且添加注释

添加文件 add

工作区的文件添加至暂存区

bash
git add file1 file2 # 添加一个或多个文件到暂存区
git add dir         # 添加指定目录到暂存区,包括子目录
git add .           # 添加当前目录下的所有文件到暂存区

查看状态

查看当前分支下工作区暂存区的状态

bash
git status [-s]   # -s (可选) 表示简短的输出结果

可以得到如下输出

bash
On branch dev             # 当前所处的分支
Your branch is ahead of 'origin/dev' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:      # 已经在暂存区, 等待添加到HEAD中的文件
  (use "git reset HEAD <file>..." to unstage)

Changes not staged for commit:# 修改的文件,但是没有添加到暂存区
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)

Untracked files:                # 未跟踪的文件
  (use "git add <file>..." to include in what will be committed)

显示索引文件和当前HEAD提交之间的差异,在工作树和索引文件之间有差异的路径以及工作树中没有被Git跟踪的路径。 第一个是通过运行git commit来提交的; 第二个和第三个是你可以通过在运行git commit之前运行git add来提交的。 更多请阅读:git status命令

git log 查看提交日志

git log 命令用于查看历史提交的信息,包含了提交id(哈希 c)、作者、邮箱、日期时间和提交信息等

bash
$ git log
commit 82484f07fd6b5468036be84b4695a8206e0ccc83
Author: <your_name> <your_email@mail.com>
Date:   Tue Feb 21 09:20:39 2023 +0800

    🚚 chore: move worksapce

--online 参数只输出提交id(哈希 SHA1值)和提交信息

bash
$ git log --oneline
82484f07 🚚 chore: move worksapce
a88ccec2 🛠 build: add images to project

40位SHA1值过长,实际上在项目中取前7位产生冲突的概率很小,因此 git 使用前7位对提交记录进行表示
82484f07fd6b5468036be84b4695a8206e0ccc83 -> 82484f07

  • --graph 可以显示分支结构,不过在终端中还是有些麻烦,无所谓,在 vscode 中插件 Git Graph 会出手
  • --stat 会显示哪些文件进行了修改
  • --author="<author_name>" 将会过滤出指定用户 <author_name>
  • --after '02-10-2023'" / --before '02-21-2023'" 限定范围日期

🌿 分支操作

git 中的分支,本质上是个指向 commit 对象的可变指针。

HEAD 当前分支引用的指针,指向某一次 commit (默认上一次 commit ),通过 HEAD 可以知道当前工作在哪一个分支上。而该指针存储于 `.git/HEAD` 文件内,打开文件内容为
ref: refs/heads/dev

ref 指向的则是 .git/refs/heads ,打开该目录,可以看到当前有 devmaster 两个文件

HEAD 分支

命令 git show head 是用来检查 Head 的状态的,该命令将显示 Head 的位置。

bash
git show HEAD

🌿 分支命名

Git 常见分支命名

分支说明
main/master主分支。正式版本,最稳定的
dev开发分支。功能最新最全的分支,在此分支上修复 bug
feat-*新功能分支。某个功能正在开发阶段,在此分支上写 bug
release-*发布定期要上线的功能
bug-*/issue-*修复线上代码的 bug

🌿 查看分支

bash
git branch    # 查看本地分支,带 * 表示当前分支
git branch -a # 查看本地和远程分支

🌿 创建和切换分支

查看分支

bash
git branch <branch_name>      # 新建分支
git checkout <branch_name>    # 切换至指定分支
git checkout -b <branch_name> # 新建并切换至新分支

new branch

🌿 向远端推送分支

bash
git push origin <branch_name> # 推送分支到远程

🌿 获取远程代码

从远程获取代码主要有两种方式

  • git pull
  • git fetch (推荐)

git pull 将从远程 (origin) 拉取最新版本到本地,自动合并(merge)

bash
git pull origin master

这种方法适用于 vscode 的时候, vscode 会自动弹出冲突位置,解决完成冲突后,提交一次 commit 就可以推送代码

git fetch 从远程拉取最新版本到本地,不会自动合并(merge) ,

bash
git fetch        # 拉取远程全部分支
git fetch origin # 同上一条
git fetch origin main # 拉取远程的 main 分支

查看文件差异

bash
git log -p main..origin/main

该命令执行完后需要执行 git merge 将远程分支合并至本地分支

bash
git merge origin/main

这种方式适合于终端合并代码

每天工作开始之前,没啥事就 pull 自己的分支,然后 fetch 整个项目,开始愉快的一天

🌿 合并分支!!!

实际上合并分支有 git mergegit rebase 两种方式

「git merge」 将两个或两个以上的开发历史加入(合并)一起。

例如将 dev 分支合并至 main 分支

bash
git checkout main # 切换分支
git merge dev     # 将 dev 合并进当前分支

这时候合并会产生一个新的提交(76f1e80) 'Merge branch 'master' of...

git-merge

「git rebase」 名为变基
bash
git checkout dev  # 切换分支
git rebase main   # 将 main 变基至当前分支

在 dev 分支上执行 rebase 则会将 main 分支最后的提交作为基点,逐个应用 dev 的每个更改。

可以看出, rebase 的方式像是把当前分支的历史提交“接”到了 main 分支最新提交的后面,因此使用 rebase 则不会像 merge 那也因为合并而产生新的提交

在工作环境中,我们会在自己的分支上进行开发

git-rebase

关于 mergerebase 在知乎上有个讨论 ,而 “rebase 的最大好处并不是消除 merge ,而是避免 merge 的交织”,因此建议如下以避免历史提交中无意义的交织:

  • 将主分支 rebase 到自己的分支上
  • 将自己分支的修改 merge 到主分支上

另外,尤雨溪曾​说过:“多用 rebase”

🌿 分支合并策略

⬅️ 版本回退

撤回

git reflog 能列出你在 Git 上的所有操作记录。你只要找到 HEAD@{index} 前面所对应的操作索引,就可以 git reset 进行退回:

bash
git reflog
git reset HEAD@{index}

使用时需将HEAD@{index}替换为对应索引。

获取指定提交 cherry-pick

https://zhuanlan.zhihu.com/p/58962086

git reset 重置

git reset [commit-hash]~1 这个命令是用来将当前分支的 HEAD 指针移动到指定提交的前一个提交。这里详细解释一下:

git reset 策略主要有如下几种:

  • Soft Reset

📥 储藏代码

背景:为什么需要储藏代码呢?
某一天,在 feature 分支上快乐地写着新的 BUG,突然,因为不可抗力因素必须切换到 main 分支或者其他分支上 checkout 一个 hotfix 分支出来紧急修复之前写过的 BUG。但是当前的代码还没有写完,也没有提交,更不可能删了不要,因此就需要储藏当前分支的代码

执行 git stash 对当前的分支的修改进行储藏,之后

bash
git stash
# 对储藏添加标记
git stash save [stashMessage]

执行后,用 git status 查看分支状态,工作区是干净的。就可以切换分支进行修改。

git stash list 可以查看储藏记录列表

bash
git stash list

储藏记录每一条标识如下

bash
stash@{index}: WIP on [分支名]: [最近一次的commitID] [最近一次的提交信息]

修改完成后再取出储藏的修改,二选一

bash
git stash pop                 # 取出最近一次储藏的修改到工作区
git stash apply stash@{index} # 取出指定index的储藏的修改到工作区中
git stash drop stash@{index}  # 将指定index的储藏从储藏记录列表中删除

🐛 修复 BUG

https://blog.csdn.net/Huang_ZX_259/article/details/122657055

🧽 压缩提交

参考: https://blog.csdn.net/starperfection/article/details/110764759

当前工作区中有 4 次提交

bash
$ git log
commit d00e013a020c63e6b11fbb6483b1875f2c260464 (HEAD -> main)
Author: HenryZhuHR <26506195@qq.com>
Date:   Sat Aug 26 05:25:21 2023 +0000

    commit 3

commit 3f26195229713952d4134579351cb0a996cc2201
Author: HenryZhuHR <26506195@qq.com>
Date:   Sat Aug 26 05:25:14 2023 +0000

    commit 2

commit 8a27bc4bab19cbcb0ccb1c2bec92eb9d50de2fc2
Author: HenryZhuHR <26506195@qq.com>
Date:   Sat Aug 26 05:25:03 2023 +0000

    commit 1

commit f8db341ebd9b71a7ddfb59b65baaddcc6373e74b
Author: HenryZhuHR <26506195@qq.com>
Date:   Sat Aug 26 05:24:17 2023 +0000

    init
(END)
bash
git rebase -i HEAD~n  # 压缩最近 n 次提交
git rebase -i [start_hash] [end_hash] # 压缩的范围
bash
git rebase -i HEAD~3  # 压缩最近 3 次提交
git rebase -i 8a27bc f8db34 # 压缩 8a27bc 到 f8db34 之间的提交

执行后会打开一个编辑器,内容如下, pick 修改为 squash 或者 s,然后保存退出

bash
pick 8a27bc4 commit 1
pick 3f26195 commit 2
pick d00e013 commit 3
bash
commit 447daa0a3490147f40a7ef4b53a3c141f21b5636 (HEAD)
Author: HenryZhuHR <26506195@qq.com>
Date:   Sat Aug 26 05:25:03 2023 +0000

    commit test

commit f8db341ebd9b71a7ddfb59b65baaddcc6373e74b
Author: HenryZhuHR <26506195@qq.com>
Date:   Sat Aug 26 05:24:17 2023 +0000

    init
(END)

👦 子模块

在 git 中,可以通过子模块的方式将一个 git 仓库嵌套到另一个 git 仓库中,这样可以方便的管理多个仓库之间的依赖关系,或者可以避免单个仓库过大。

官方教程:Git 子模块

bash
usage: git submodule [--quiet] [--cached]
   or: git submodule [--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>]
   or: git submodule [--quiet] status [--cached] [--recursive] [--] [<path>...]
   or: git submodule [--quiet] init [--] [<path>...]
   or: git submodule [--quiet] deinit [-f|--force] (--all| [--] <path>...)
   or: git submodule [--quiet] update [--init [--filter=<filter-spec>]] [--remote] [-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--[no-]recommend-shallow] [--reference <repository>] [--recursive] [--[no-]single-branch] [--] [<path>...]
   or: git submodule [--quiet] set-branch (--default|--branch <branch>) [--] <path>
   or: git submodule [--quiet] set-url [--] <path> <newurl>
   or: git submodule [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]
   or: git submodule [--quiet] foreach [--recursive] <command>
   or: git submodule [--quiet] sync [--recursive] [--] [<path>...]
   or: git submodule [--quiet] absorbgitdirs [--] [<path>...]

添加子模块

通常来说,为仓库添加子模块的步骤如下[6]

在当前 git 仓库下添加子模块

bash
git submodule add <url> <path>
git submodule add git@github.com:HenryZhuHR/homepage-docs.git docs
  • <url> 是子模块的地址,
  • <path> 是子模块的路径

获取子模块的仓库

方法一:获取父仓库时,使用 --recursive 参数,可以一并获取子模块的仓库,需要注意 submodule 默认是不在任何分支上的,它指向父项目存储的 submodule commit id。

bash
git clone --recursive <url>

方法二:获取父仓库后,使用 git submodule initgit submodule update 命令获取子模块的仓库

bash
git clone <url>
cd <repo>
git submodule init
git submodule update

📚 参考资料

⏰ 最后更新于: