Git#

配置仓库#

配置用户名和邮箱
git config --global user.name "zhyantao"
git config --global user.email "yantao.z@outlook.com"
VSCode 未修改代码,但显示变更
# 修改当前仓库的本地配置(仅对当前仓库应用)
git config --replace-all core.filemode false
git config --replace-all core.autocrlf false

# 清除 Git 的索引(或称为缓存)-- 慎用
git rm --cached -r .
git reset HEAD .

也可以在克隆仓库时自动配置 Git 仓库(通过 Git 模板钩子实现)。

Note

core.filemode = false 会使 Git 忽略文件执行权限的变更,这在跨平台开发时可能是需要的,但如果您需要跟踪文件权限变化,请勿使用此设置。

# 创建全局模板目录
git config --global init.templateDir ~/.git-template

# 创建钩子目录
mkdir -p ~/.git-template/hooks

# 创建 post-checkout 钩子
cat > ~/.git-template/hooks/post-checkout << 'EOF'
#!/bin/sh
# 只在初始克隆时执行一次
if [ "$1" = "0000000000000000000000000000000000000000" ]; then
    echo "Initializing Git repository configuration..."

    git config --replace-all core.filemode false
    echo "  core.filemode set to false"

    git config --replace-all core.autocrlf false
    echo "  core.autocrlf set to false"

    echo "Git configuration initialized."
fi
EOF

# 使钩子可执行
chmod +x ~/.git-template/hooks/post-checkout

# 设置新仓库使用模板
git config --global init.templatedir '~/.git-template'
GitHub 不显示头像

如果你在 Github 上修改了提交邮箱,而没有修改本地提交邮箱的话,会发现你的头像在提交记录上无法显示。因此,本地的提交邮箱应当与远程仓库保持一致。修改 ~/.gitconfig 可解决问题。

典型工作流程#

  • 工作目录 (Working Directory):你正在编辑的文件。

  • 暂存区 (Staging Area / Index):通过 git add 添加的文件,准备下次提交。

  • 版本库 (Repository / HEAD):通过 git commit 提交后的历史记录。

@startuml
start

:git clone <repo url>;
note right:下载远程仓库代码

repeat
  :编辑文件;

  if (拉取别人的代码?) then (否)
  else (是)
    :git stash;
    note right:临时保存工作目录的修改
    :git pull;
    note right:同步别人的修改
    :git stash pop;
    note right:恢复最近保存的 stash 并删除 stash 记录
  endif

  repeat
    repeat
      :git add <file>...;
      note right:添加到暂存区

      backward:git reset HEAD <file>...;
    repeat while (撤销暂存?) is (Yes) not (No)

    repeat
      :git commit;
      note right:创建新提交

      backward:git reset --soft HEAD~1;
    repeat while (撤销提交(保留暂存)?) is (Yes) not (No)

    backward:git reset --mixed HEAD~1;
  repeat while (撤销提交和暂存?) is (Yes) not (No)

  backward:git reset --hard HEAD~1;
repeat while (彻底丢弃更改?) is (Yes) not (No)

repeat
  :git push [--force] <remote> <branch>;
  note right:推送到远程

  backward:git revert HEAD;
repeat while (安全撤销提交?) is (Yes) not (No)

stop
@enduml

# 列出所有已配置的远程仓库及其对应的 URL
git remote -v

# 显示所有远程跟踪分支
git branch -r

# 更新指定远程仓库的 URL 地址
git remote set-url <remote> <url>

# 显示工作目录和暂存区的状态 (变更文件列表)
git status

# 显示工作目录与暂存区之间的差异
git diff

# 显示两次特定提交之间的差异对比
git diff HEAD~1 HEAD~2

# 修改最近一次提交的提交信息 (不会创建新的提交)
git commit --amend -m "<message>"

# 基于当前提交创建新分支
git branch <branch>

# 重命名本地分支
git branch -m <old-name> <new-name>

# 安全删除已合并的本地分支
git branch -d <branch>

# 删除远程分支
git push origin --delete <branch>

# 显示当前分支的提交历史 (按时间倒序)
git log

# 逐行显示文件的修改历史 (作者、时间、提交信息)
git blame <filename>

# 将指定提交的更改应用到当前分支
git cherry-pick <commit>

# 将连续多个提交应用到当前分支 (包含首尾提交)
git cherry-pick <first-commit>^..<last-commit>

# 将指定提交合并到当前分支
git merge <commit>

# 将指定分支的所有更改合并到当前分支
git merge <branch>

# 将当前分支的提交在目标分支上重新应用 (变基操作)
git rebase <branch>

# 在当前提交上创建一个新标签
git tag <tag>

# 删除本地标签
git tag -d <tag>

# 将标签推送到远程仓库
git push <remote> <tag>

submodule#

管理子库
# 添加 submodule 到现有项目
git submodule add https://github.com/username/subrepo.git path/to/subrepo

# 从当前项目移除 submodule
cat .gitmodules | grep path
git submodule deinit -f <submodule-path>
rm -rf .git/modules/<submodule-path>
git rm -f <submodule-path>

# 更新 submodule 的 URL
# 首先修改 .gitmodules 文件中的 url 属性
# 如果已经初始化了,先删除 submodule 在本地相应的文件夹
git submodule sync
git submodule update --init --recursive

# 更新 submodule 为远程项目的最新版本
git submodule update --remote

# 更新指定的 submodule 为远程的最新版本
git submodule update --remote <submodule-path>

# 检查 submodule 是否有提交未推送,如果有,则使本次提交失败
git push --recurse-submodules=check

# 先推送 submodule 的更新,然后推送主项目的更新
# 如果 submodule 推送失败,那么推送任务直接终止
git push --recurse-submodules=on-demand

# 推送所有 submodule,但不推送 superproject
git push --recurse-submodules=only

# 与 only 相反,只推送 superproject,不推送其他 submodule
git push --recurse-submodules=no

# 拉取所有子仓库(fetch)并 merge 到所跟踪的分支上
git pull --recurse-submodules

# 查看 submodule 所有改变
git diff --submodule

# 对所有 submodule 执行命令,非常有用。如 git submodule foreach 'git checkout main'
git submodule foreach <arbitrary-command-to-run>

代码提交规范#

类型

说明

feat

新功能

fix

修复漏洞

docs

文档

style

格式(不影响代码运行的变动)

refactor

重构(不改变功能的代码变动)

perf

优化相关,比如提升性能、体验

test

增加测试

chore

构建过程或辅助工具的变动

revert

回滚到上一个版本

merge

代码合并

sync

同步主线或分支的变动

typo

更改一些拼写错误

修改 Git Commit 历史

参考 git-filter-repo(1) (htmlpreview.github.io)

(1) 环境部署

  1. 下载仓库:newren/git-filter-repo.git

  2. 将仓库根目录添加到系统环境变量。

(2) 修改历史提交记录

如果你修改了邮箱,你在 Windows 上设置的提交邮箱与 GitHub 上设置的邮箱不一致,历史提交信息中的头像可能会空白。这种情况下下,可以使用下面的方法解决。

创建 mailmap.txt,格式如下所示(注:username 允许存在空格,尖括号不用去掉):

cat <<EOF | tee ../mailmap.txt
User Name <email@addre.ss>                                   # 本次提交的用户名和邮箱
<new@email.com> <old1@email.com>                             # 只修改邮箱
New User Name <new@email.com> <old2@email.com>               # 同时修改用户名和邮箱
New User Name <new@email.com> Old User Name <old3@email.com> # 同时修改用户名和邮箱
EOF

一个简单的示例如下所示:

cat <<EOF | tee ../mailmap.txt
<yantao.z@outlook.com> <zh6tao@gmail.com>
zhyantao <yantao.z@outlook.com> 非鱼 <zh6tao@gmail.com>
EOF

cd 到仓库的根目录,运行下面的命令:

git filter-repo --mailmap ../mailmap.txt

在开发过程中,发现将密码或私钥上传到 GitHub 上,思考如何在不删除仓库的情况下,仅修改敏感信息来将密码隐藏掉。首先,创建 replacements.txt,添加如下变更内容:

cat <<EOF | sudo tee ../replacements.txt
PASSWORD1                       # 将所有提交记录中的 'PASSWORD1' 替换为 '***REMOVED***' (默认)
PASSWORD2==>examplePass         # 将所有提交记录中的 'PASSWORD2' 替换为 'examplePass'
PASSWORD3==>                    # 将所有提交记录中的 'PASSWORD3' 替换为空字符串
regex:password=\w+==>password=  # 使用正则表达式将 'password=\w+' 替换为 'password='
regex:\r(\n)==>$1               # 将所有提交记录中的 Windows 中的换行符替换为 Unix 的换行符
EOF

cd 到仓库的根目录,运行下面的命令:

git filter-repo --replace-text ../replacements.txt

(3) 提交到远程仓库

git filter-repo 工具将自动删除你配置的远程库。使用 git remote set-url 命令还原远程库:

git remote add origin git@github.com:username/repository.git

需要强制推送才能将修改提交到远程仓库:

git push origin --force --all
! [remote rejected] main -> main (protected branch hook declined)
remote: error: GH006: Protected branch update failed for refs/heads/main.
remote: error: Cannot force-push to this branch
To github.com:zhyantao/cc-frontend-preview.git
! [remote rejected] main -> main (protected branch hook declined)

解决方法:Settings > General > Danger Zone > Disable branch protection rules

要从标记版本删除敏感文件,还需要针对 Git 标记强制推送:

git push origin --force --tags

分支命名规范#

分支类型

命名

说明

主分支

master / main

对外发布的正式版本,始终保持稳定可发布状态

开发分支

dev / develop

集成最新功能的开发主线,功能分支的合并目标

功能分支

feature/<feature-name>

从 dev 切出,用于开发新功能,完成后合并回 dev 并删除

发布分支

release/<version>

从 dev 切出,用于发布前的测试与版本冻结,例如 release/v1.2.0

修复分支

fix/<bug-name>

从 dev 切出,用于修复测试阶段发现的普通 BUG

紧急修复分支

hotfix/<bug-name>

从 master 切出,用于修复线上紧急 BUG,完成后需合并回 master 和 dev

冲突处理

有时想把 <other-branch> 的内容合并到当前所在分支,使用命令 git fetch <remote> <other-branch> 和 git merge FETCH_HEAD 后,发现 有冲突。冲突的文件会有类似如下所示的结果:

<<<<<<< HEAD (冲突开始的位置)
最新的修改
=======
上一次提交的修改
>>>>>>> 上一个分支的名称 (冲突结束的位置)

因此,我们的目标就是对冲突开始和结束之间的部分进行删减。 解决完冲突后,继续使用命令 git add 和 git commit 命令即可完成后续开发工作。

error: The following untracked working tree files would be overwritten by checkout

git clean -fd

标签命名规范#

标签命名遵循 主版本号.次版本号.修订号 的规则,例如 v1.2.3 是版本 1.2 的第 4 次修订。以下是版本号的升级规则:

  • 优化已经存在的功能,或者修复 BUG:修订号 +1;

  • 新增功能:次版本号 +1;

  • 架构变化,接口变更:主版本号 +1。

gitignore#

匹配规则
  • gitignore 只匹配其所在目录及子目录的文件。

  • 已经被 git track 的文件不受 gitignore 影响。

  • 子目录的 gitignore 文件规则会覆盖父目录的规则。

# 忽略特定文件
ModelIndex.xml
ExportedFiles.xml

# [] 匹配包含在 [] 范围内的任意字符
[Mm]odel/[Dd]eployment

# 使用 \ 加空格匹配包含空格的文件或文件夹
Program\ Files

# 忽略名为 hello 的目录和该目录下的所有文件,但是不会匹配名为 hello 的文件
hello/

# 忽略名为 hello 的文件
hello

# 忽略名为 b 的文件,该文件在文件夹 a 下,且该文件的路径为 a/b 或 a/任意路径/b
a/**/b

# 强制包含指定文件夹,* 匹配除了 / 之外任意数量的任意字符串
!Model/Portal/*/SupportFiles/[Bb]in/

# 强制包含指定文件,? 匹配除了 / 之外的任意一个字符
!Model/Portal/PortalTemplates/?/SupportFiles/[Bb]in

在 shell 中显示 git 分支#

方法一:使用 romkatv/gitstatus 提供的服务:

git clone --depth=1 https://github.com/romkatv/gitstatus.git ~/gitstatus
echo 'source ~/gitstatus/gitstatus.prompt.sh' >> ~/.bashrc
source ~/.bashrc

方法二:打开 ~/.bashrc 做如下修改:

# display git branch on bash
git_branch() {
branch="`git branch 2>/dev/null | grep "^\*" | sed -e "s/^\*\ //"`"
if [ "${branch}" != "" ];then
    if [ "${branch}" = "(no branch)" ];then
        branch="(`git rev-parse --short HEAD`...)"
    fi
    echo -e ":\033[01;32m$branch\033[00m"
fi
}

PS1="${PS1}\$(git_branch) " # 补充到 PS1 变量上(赋值时等号两侧不能有空格)

Post Git 提供了显示 Git 分支的功能,安装 Posh Git,请执行以下步骤:

  1. 以管理员身份启动 PowerShell。

  2. 修改执行策略以允许脚本运行:

    Set-ExecutionPolicy RemoteSigned
    
  3. 安装 Posh Git 模块,指定范围为当前用户并强制安装:

    Install-Module posh-git -Scope CurrentUser -Force
    
  4. 导入 Posh Git 模块以便使用:

    Import-Module posh-git
    
  5. 将 Posh Git 添加到 PowerShell 配置文件中,以便对所有会话有效:

    Add-PoshGitToProfile -AllHosts
    

卸载 Posh Git,请执行以下步骤:

  1. 以管理员身份运行 PowerShell。

  2. 删除 Posh Git 模块:

    Uninstall-Module posh-git
    
  3. 编辑 PowerShell 配置文件以移除 Posh Git 模块的导入命令。打开配置文件:

    notepad $PROFILE
    

    然后,删除文件中包含 Import-Module posh-git 的行。

brew install romkatv/gitstatus/gitstatus
echo "source $(brew --prefix)/opt/gitstatus/gitstatus.prompt.zsh" >>! ~/.zshrc

brew install powerlevel10k
echo "source $(brew --prefix)/share/powerlevel10k/powerlevel10k.zsh-theme" >>~/.zshrc

# 若 powerlevel10k 没有自动弹出安装字体的程序,需要手动安装字体
brew untap homebrew/cask-fonts
brew update
brew install --cask font-meslo-lg-nerd-font
brew install --cask font-meslo-for-powerlevel10k
sudo atsutil databases -remove
atsutil server -shutdown

# 重启 macOS,然后搜索字体是否成功安装,如果能搜到,说明安装成功了
system_profiler SPFontsDataType | grep -i "MesloLGS NF"

# UI 操作:设置 iTerm2 和 VS Code 的字体样式
iTerm2 -> Settings -> Profiles -> Text -> Font -> MesloLGS NF
Code -> 首选项 -> 设置 -> 搜索 -> Editor: Font Family -> MesloLGS NF, Menlo, Monaco, 'Courier New', monospace

自动补全#

# 下载 git-completion.bash
wget https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.bash

# 将 git-completion.bash 放在服务器上
cp ~/git-completion.bash /etc/bash_completion.d/

# 使 git-completion.bash 生效
. /etc/bash_completion.d/git-completion.bash

# 编辑 /etc/profile 添加如下内容
if [ -f /etc/bash_completion.d/git-completion.bash ]; then
    . /etc/bash_completion.d/git-completion.bash
fi

# 使 /etc/profile 生效
source /etc/profile

Post Git 提供了自动补全的功能,安装 Posh Git,请执行以下步骤:

  1. 以管理员身份启动 PowerShell。

  2. 修改执行策略以允许脚本运行:

    Set-ExecutionPolicy RemoteSigned
    
  3. 安装 Posh Git 模块,指定范围为当前用户并强制安装:

    Install-Module posh-git -Scope CurrentUser -Force
    
  4. 导入 Posh Git 模块以便使用:

    Import-Module posh-git
    
  5. 将 Posh Git 添加到 PowerShell 配置文件中,以便对所有会话有效:

    Add-PoshGitToProfile -AllHosts
    

卸载 Posh Git,请执行以下步骤:

  1. 以管理员身份运行 PowerShell。

  2. 删除 Posh Git 模块:

    Uninstall-Module posh-git
    
  3. 编辑 PowerShell 配置文件以移除 Posh Git 模块的导入命令。打开配置文件:

    notepad $PROFILE
    

    然后,删除文件中包含 Import-Module posh-git 的行。

制作补丁#

git format-patch 是 Git 推荐使用的补丁制作工具。与 diff 相比,它生成的补丁包含作者信息、提交时间、提交日志等元数据,更适合在协作开发中使用。

# (推荐)生成最近 n 次提交的补丁
git add <files>
git commit -m "Bump iperf3 from 3.20 to 3.21"
git format-patch -1                              # 最近 1 次提交
git format-patch -2                              # 最近 2 次提交

# (推荐)生成指定 commit 的补丁(仅包含该 single commit)
git format-patch -1 <commit_id>

# (推荐)生成两个 commit 之间的所有补丁
git format-patch <since_commit>..<until_commit>  # since~until 之间的提交
git format-patch <since_commit>..                # since 之后的所有提交

# (推荐)生成当前分支相对于 master 的独特提交
git format-patch master                          # 当前分支比 master 多的提交

# (可选)将所有补丁合并成一个文件(⚠️ 可能丢失邮件线索信息)
git format-patch master --stdout > all-changes.patch

# (补充)纯 diff 风格补丁(不推荐,仅作对比)
git diff > patch.diff                            # 对已 track 的文件制作补丁
git diff <old_file> <new_file> > patch.diff      # 对未 track 的文件制作补丁

Tip

git format-patch 生成的补丁文件默认命名格式为 0001-commit-subject.patch,文件名按提交顺序自动编号,便于批量应用。

应用补丁#

使用 git am(apply mailbox)应用 format-patch 生成的补丁,可以完整保留作者、提交时间、提交日志等信息。

# 应用单个补丁
git am 0001-fix-bug.patch

# 应用目录下所有补丁(按文件名顺序)
git am *.patch

# 应用时遇到冲突,解决后继续
git add . && git am --resolved

# 跳过当前有冲突的补丁
git am --skip

# 放弃本次应用操作
git am --abort
应用传统 diff 补丁(不推荐)
# (单文件打补丁)patch.diff 必须包含文件名,根据文件名应用补丁
patch < patch.diff

# (多文件打补丁)patch.diff 必须包含文件名,根据文件名应用补丁
cd <project_dir>
patch -p1 < patch.diff  # p1 表示忽略第 1 级目录,即 <project_dir>

# (多文件打补丁)patch.diff 必须包含文件名,根据文件名应用补丁
cd <project_dir>/..
patch -p0 < patch.diff

# 对指定文件应用补丁
patch path/to/file < patch.diff

# 对 input_file 应用补丁,但是将应用补丁后的结果写入 output_file
patch path/to/input_file -o path/to/output_file < patch.diff

# 反向应用补丁,即将打过补丁的文件还原
patch -R < patch.diff

Note

关于 -p<n> 参数的含义(忽略 n 级目录)

以 format-patch 生成的补丁头部为例:

--- a/src/module/test.c
+++ b/src/module/test.c

目录层级解析:

  • a → 第 1 级目录

  • src → 第 2 级目录

  • module → 第 3 级目录

不同参数的行为:

  • patch -p0:使用完整路径 a/src/module/test.c

  • patch -p1:去掉 a/,在当前目录找 src/module/test.c

  • patch -p2:去掉 a/src/,在当前目录找 module/test.c

Tip

使用 git am 应用补丁时无需关心 -p 参数,系统会自动处理路径信息。

format-patch vs diff

特性

git format-patch

git diff

作者信息

包含

不包含

提交时间

包含

不包含

提交日志

包含

不包含

提交 SHA

包含

不包含

应用后生成独立提交

git am 自动生成

需手动 git commit

多补丁排序

自动编号

需手动管理

适用场景

开源协作、邮件审阅、代码合入

临时对比、本地快速同步

Tip

通用原则:

  • 推荐:只要是传送给别人的补丁,优先使用 git format-patch + git am 流程

  • 可选:仅自己临时记录改动,或补丁只用于单个文件同步时,可以考虑 git diff + patch

公私仓库分离#

私有仓库会合入自己的代码,但是仍然需要和公开仓库保持同步。

# 一次性配置
git remote set-url origin git@gitee.com:zhyantao/stm32mp157-m4.git
git remote add upstream https://gitee.com/weidongshan/stm32mp157-m4.git

# 拉取官方最新
git fetch upstream

# 切到你的工作分支
git checkout master

# 方式 A:合并(保留分叉历史,产生 merge commit)
git merge upstream/master

# 方式 B:变基(历史线性,但会重写你的提交 SHA)
# git rebase upstream/master

# 推送
git push origin master