git 实践

本文主要收集有关 git 相关的优化及使用技巧。

git

git 是使用最广的分布式代码版本控制系统.

参考文档:

working

在 Git 中,working directory(工作目录)是指当前正在进行编辑和修改的文件的目录。它是与 Git 仓库中的版本控制系统相对应的文件系统中的一个目录。

当在工作目录中进行修改时,Git 会跟踪这些更改并将其标记为未暂存的更改。这意味着这些更改尚未被提交到 Git 仓库中。工作目录中的文件可以包括新创建的文件、已修改的文件和已删除的文件。

工作目录是进行开发和编辑的主要区域。可以在工作目录中添加、修改和删除文件,然后使用 Git 的各种命令来管理这些更改。当准备好将更改提交到 Git 仓库时,可以使用 git add 命令将更改暂存到暂存区,然后使用 git commit 命令将更改提交到仓库。

总结来说,工作目录是进行开发和编辑的区域,其中包含了对文件的修改。Git 会跟踪这些修改并提供一系列命令来管理和提交这些更改。

index

在 Git 中,"index" 是指暂存区(也称为索引或缓存)。暂存区是 Git 的一个重要概念,它充当了工作区和最终提交的代码库之间的中间层。

当在工作区修改了文件后,Git 并不会立即将这些修改提交到代码库中。相反,Git 要求将修改的文件添加到暂存区,然后再将暂存区的内容提交到代码库中。这个过程分为两个步骤:

  1. 将修改的文件添加到暂存区:使用 git add 命令将工作区的修改添加到暂存区。这样,Git 就会将这些修改纳入下一次提交的范围内。

  2. 将暂存区的内容提交到代码库:使用 git commit 命令将暂存区的内容提交到代码库中。这样,Git 就会创建一个新的提交,包含了暂存区的内容,并将其添加到代码库的历史记录中。

通过使用暂存区,可以对提交进行精细的控制。可以选择性地将修改的文件添加到暂存区,而不是一次性提交所有的修改。这样可以帮助构建更有意义的提交,使代码库的历史记录更加清晰和可读。

在 Git 中,HEAD 是一个指向当前所在分支最新提交的指针。它可以被看作是当前工作树的快照,指示了当前所在分支的最新提交。

HEAD 在 Git 中有两个主要的作用:

  1. 标识当前所在分支的最新提交:当在 Git 中进行提交操作时,HEAD 会自动更新为最新的提交。这意味着 HEAD 始终指向当前所在分支的最新提交。

  2. 作为切换分支的指针:当切换分支时,HEAD 会随之移动到新分支的最新提交。这样,就可以在不同的分支之间进行切换,并查看每个分支的最新提交。

在 Git 中,HEAD 通常指向一个分支引用,例如 refs/heads/master。这表示 HEAD 当前指向 master 分支的最新提交。当在 Git 中进行提交操作时,新的提交将被添加到当前所在分支的提交历史中,并且 HEAD 将指向这个新的提交。

除了指向分支引用,HEAD 还可以指向一个具体的提交哈希值。这种情况下,处于 “分离头指针” 状态,意味着不再位于任何分支上,而是直接在某个特定的提交上工作。

总之,HEAD 在 Git 中是一个重要的指针,用于标识当前所在分支的最新提交。它允许在不同的分支之间进行切换,并且在提交操作时自动更新为最新的提交。

git auto-complete

bash auto-complete

使用 git 提供的补全脚本

1
echo "source /usr/share/bash-completion/completions/git" >> ~/.bashrc

powershell auto-complete

使用 choco install git-posh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Set your PowerShell execution policy
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force

# Install Chocolatey
iwr https://chocolatey.org/install.ps1 -UseBasicParsing | iex

# Install Chocolatey packages
choco install poshgit -y
choco install git.install -y
choco install conemu -y

# Install PowerShell modules
Install-PackageProvider NuGet -MinimumVersion '2.8.5.201' -Force
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
Install-Module -Name 'posh-git'

reference

git EOL

[!TIP]
优化文件从 windows 挂进容器时,git 触发多个文件更改问题,tips-and-tricks

1
2
3
4
# .gitattributes
* text=auto eol=lf
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf

git LFS

git 配置参考 LFSDevcontainer

LFS 配置示例如下,将常用的 AI 模型文件作为 LFS 形式提交:

1
2
3
4
5
6
7
# Model Format
*.onnx filter=lfs diff=lfs merge=lfs -text
*.pt filter=lfs diff=lfs merge=lfs -text
*.ckpt filter=lfs diff=lfs merge=lfs -text
*.meta filter=lfs diff=lfs merge=lfs -text
*.index filter=lfs diff=lfs merge=lfs -text
checkpoint filter=lfs diff=lfs merge=lfs -text

git rebase

links:

modify history

将遗漏的提交提交到历史中倒是第 2 个记录中

1
2
3
4
5
6
7
8
9
10
11
12
13
# rebase 提取两个记录
git rebase -i HEAD~2

# edit in rebase view
pick 1fc6c95 do something
edit dd1475d the commit you want to change # 将 pick 改为 edit 标记

# 添加遗漏的更改
git add some other changes
git commit --amend --no-edit

# 进入下一个标记操作,若有冲突解决重提,继续continue,直到结束为止
git rebase --continue

Squash in git

通过 Interactive Rebase 实现,将第一个提交后面的提交改成 s,保存退出,按照提示操作即可。

1
2
# rebase 操作后续 3 个提交记录
git rebase -i HEAD~3

通过 merge 加 --squash 参数的方式,本地分支被完整的保留下来。

1
2
3
git merge --squash feat/add-coin
git add a.txt
git commit -m "feat: support add coin"

squash reference

git reset

参考文档:

1
2
3
4
5
6
7
# 将文件从staged转为unstaged
git reset <file>
git restore --staged <file>
# 将所有的文件转为unstaged
git reset HEAD
# undo the last commit while keeping the changes from that commit in the staging area.
git reset --soft ^HEAD

git restore

参考文档:

1
2
# discard the unstaged changed file
git restore <file>

git remote

1
2
3
4
5
6
# 添加远程
git remote add <remote-name> <url>
# 查看本地注册的远程
git remove -v
# 删除关联的远程
git remote remove <remote-name>

git with --depth

仓库过大时,使用 depth 可加快拉取代码。

1
2
3
4
5
6
# --depth 1
git clone --depth 1 url-to-repos
# 后续再把未同步的远程历史同步到本地
git fetch --unshallow
# 拉取远程分支branch到本地分支local-branch(通常等于branch)
git fetch <remote> <branch>[:<local-barnch>=<branch>] --depth 1

git config

config

查看所有的 git 配置位置,system/global/local 配置。

1
2
3
4
# 查看配置及配置所属位置
git config --list --show-origin
git config --global user.name "Your Username"
git config --global user.email "your.email@example.com"

credential

Git 的全局配置,用于存储凭据,比如用户名和密码,这样在与远程 Git 仓库交互时就不需要每次都输入它们。

1
git config --global credential.helper store

http

1
git config --global http.sslVerify false

proxy

1
git config --global url.https://ghproxy.com/https://github.com.insteadOf https://github.com

git log

查看历史

查看文件夹的历史提交记录

1
2
3
git log -- ./folder-history
# 查看文件更改
git log --name-only -- ./folder-history

git filter-branch

–index-filer

删除所有提交有关文件夹的历史

1
2
3
git filter-branch --force --index-filter \
'git rm -r --cached --ignore-unmatch folder_name' \
--prune-empty --tag-name-filter cat -- --all

git tag

本节总结了相关常用的 git tag 命令。

查看

1
2
3
4
5
6
# 查看所有的 tag,可直接筛选结果
git tag -l
git tag -l v0.*
git tag -l | grep -E 'v0.*'
# 查看远程 origin 下所有的 tag
git ls-remote --tags origin

创建

1
2
3
# 用 tag-name 创建 light tag
git tag [tag name]
git tag -a

同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 推送所有本地tag到远程
git push origin --tags
# 同步到远程 tag
git push origin [tag name]
# 删除
git tag -d [tag name]
# 删除本地所有 tag
git tag -d $(git tag -l)
# 删除远程 tag
git push origin :refs/tags/[tag name]
# 删除远程所有 tag
git push origin --delete $(git tag -l)
# 拉取远程 tag
git fetch origin [tag name]
git pull origin --tags
git fetch --prune-tags #删除本地仓库中已经不存在于远程仓库的标签

GitLab API tag

1
2
# 删除 tag
curl --request DELETE --header "PRIVATE-TOKEN: TOKEN-ACCESS" "https://gitlab.example.com/api/v4/projects/5/repository/tags/v1.0.0"

git branch

git branch -m

远程分支重命名后,本地需要跟踪最新的分支

1
2
3
4
git branch -m main master
git fetch origin
git branch -u origin/master master
git remote set-head origin -a

git commit

-s

在 Git commit 中,有时会看到 Signed-off-by 的标记。这是一个常见的实践,用于指示提交者已经阅读并同意相关的许可和贡献规范。

Signed-off-by 标记的格式通常是在提交信息的末尾添加一行,类似于:

1
Signed-off-by: Your Name <your@email.com>

通过在提交信息中包含 Signed-off-by 标记,提交者表明他们有权将这些更改提交到代码库,并同意遵守相关的法律和规定。这也可以用于追踪和验证提交的来源。

要配置 Signed-off-by 标记,可以按照以下步骤进行操作:

  • 打开 Git 仓库所在的命令行终端或 Git GUI 工具。

  • 在进行提交之前,确保已经设置了正确的用户名和邮箱地址。可以使用以下命令来设置:

1
2
git config --global user.name "Your Name"
git config --global user.email "your@email.com"

确保将 “Your Name” 替换为真实姓名,将 “your@email.com” 替换为真实邮箱地址。

  • 进行提交时,在提交信息的末尾添加 Signed-off-by 标记。可以手动添加,或者使用 Git 提供的提交模板或钩子来自动生成。例如,在命令行中使用以下命令进行提交:

1
git commit -s -m "Your commit message"

links:

ssh 签名

使用 ssh 配置对提交进行签名,前提是已存在 ssh 签名密钥,并注册到远程版本控制。

1
2
3
git config commit.gpgsign true               # 一般不需要全局开启,只需要在提交时指定-s即可
git config gpg.format ssh # 配置使用ssh签名
git config user.signingKey ~/.ssh/id_rsa.pub # 配置签名密钥

–cleanup

对提交消息进行清理

links:

git commit -m

1
git commit -m <title> -m <content>

git commit empty

git 空提交,用于在本地测试及保证源码不变动的情况下增加一个提交或删除相关的空提交。

增加一个空提交

Step 1. 创建一个新提交。

1
git commit --allow-empty -m "Trigger test deployment"

Step 2. 推动空提交到远程。

1
git push

删除空提交

Step 1. 查看空提交

1
2
3
4
5
git rev-list HEAD | while read commitHash; do
if [ $(git diff-tree --name-status --no-commit-id $commitHash | wc -l) -eq 0 ]; then
echo $commitHash
fi;
done

Step 2. 删除空提交

1
2
git filter-branch --commit-filter 'git_commit_non_empty_tree "$@"' -f HEAD
rm -rf .git/refs/original/ && git reflog expire --all && git gc --aggressive --prune

解释:

  • -filter-branch: 让重写分支。

  • -commit-filter: 过滤器进行提交。

  • git_commit_non_empty_tree “$@”: 包括这个参数 -commit-filter 将跳过提交离开树。 树散列不变如果没有文件被改变,因此承诺是空的。

  • -f: --force 缩写。

links:

git revert

git revert 用于回退代码版本

1
2
3
4
5
6
7
# 将 B 到 D 都 revert
git revert B^..D # git revert OLDER_COMMIT^..NEWER_COMMIT
# 通过 rebase 方式选择回滚对应的提交
git rebase -i
# 将连续多个提交回滚用一个提交完成
git revert -n OLDER_COMMIT^..NEWER_COMMIT
git commit -m "revert OLDER_COMMIT to NEWER_COMMIT"

git repo clean

安装完后,可绑定到 git 使用:

1
2
3
4
5
6
# 扫描仓库当前分支的文件,文件最小为1G,类型为tar.gz,显示前1个结果
git repo-clean --scan --limit=1G --type=tar.gz --number=1
# 在命令后添加--delete选项,则会批量删除当前分支扫描出的文件,并重写相关提交历史(包括HEAD)
git repo-clean --scan --limit=1G --type=tar.gz --number=1 --delete
# 在命令后添加--branch选项,则会扫描所有分支的文件再执行删除,并重写相关提交历史
git repo-clean --scan --limit=1G --type=tar.gz --number=1 --delete --branch=all

reference

git submodule

教程参考:

add

添加子模块,当使用远程 url 时,使用 depth 加快 clone 进行绑定。

1
2
3
4
5
6
# 使用 url 绑定子模块
git submodule add --depth=1 < repos-url > /path/to/repos-name
# 若本地已存在子项目文件夹,可直接添加
git submodule add < repos-to-path >
# 从master拉取所有子模块最新更新
git submodule foreach git pull origin master

编辑项目生成的 .gitmodules 在对应的子项目模块添加 shallow = 1,为远程同步子模块 clone 根据 --recommend-shallow 使用对应的深度同步初始化子模块。

1
2
3
4
5
6
[submodule "sub1"]
path = src/sub1
url = http://url/sub1.git
# 使用 git submodule update --init --recursive 时 clone 深度设置为 1
shallow = 1

delete

删除子模块

1
git submodule deinit [-f] <folder>
  • .gitmoduels 子模块相关描述删除

  • .git/config 子模块相关描述删掉

  • .git/modules/path/to/submodule

  • 运行 git rm --cached path/to/submodule (no trailing slash)

  • 删除其余并提交子模块非 git 相关配置

update

拉取更新子模块

1
git submodule update --init --recursive

move

移动子模块到新目录

1
git mv src/sub1 src/sub1_new

git diff

输出对比差异,在某些时候可用于 patch 补丁方面。

  • diff --git a/new.txt b/new.txt:a 版本的 new.txt(变动前) 与 b 版本的 new.txt(变动后) 比较

  • index 0e9cc9b…af0a351 100644:a 版本的在 index 的对象的哈希值 b 版本在工作区的对象的哈希值 文件信息(644 表示权限)

  • — a/new.txt:变动前的文件

  • @@ -1,2 +1,3 @@:变动前的文件从第 1 行开始,连续 2 行;变动后的文件第行开始,连续 3 行存在差异 。

  • -:红色部分表示减少的部分

  • +:绿色部分表示增加的部分

1
2
# 输出差异
git diff | out-file -enc ascii <out file>

git commit convention

一个好的提交 message 规则

  • 从主体中分离出标题并用空行隔开

  • 限制标题字符 50 以内

  • 大写标题行

  • 不需要结束标题行

  • 在主题行中使用祈使语气

  • 主体使用 72 字符换行包装

  • 使用主体解释 what、why、how

[!NOTE]
另加 Github Gist Commit Message Guidelines 及示例 wiki

git commit --amend

修改上一个提交内容,包括修改提交作者

1
git commit --amend --author="Author Name <email@address.com>"

追加到前一个提交中,并不做任何更改

1
2
git add .
git commit --amend --no-edit