跳转到内容

Git

Git 是一个分布式版本控制系统,非常流行,可以称得上是目前的行业标准。

Git 保存的代码通常被叫做仓库。仓库可以是 本地仓库,存放在本机上;也可以是 远程仓库,存放在服务器上。

有很多的服务商可以托管 Git 仓库,比如 GitHub、GitLab 等。这些服务大多都是免费的,让租不起服务器的开发者也能够与别人进行协作,推动了开源社区的发展。

代码修改后需要提交,然后变更会被记录到仓库中。

每个提交都会产生一个唯一的哈希值,你可以用这个哈希值来指定具体的单次提交。也可以用 HEAD 表示最近的提交,用 HEAD~n 来表示最近提交的前 n 个提交

提交时必须有提交信息,这些信息是给人阅读的。提交信息应该遵循一定的规范。Conventional Commits 是一个社区里常用的提交范式,如果不知道怎么写提交的话可以参考一下。

暂存使得我们可以在未正式提交前就备份代码,并且能够精确控制提交的内容。

暂存区(有时也叫索引)用于存放准备提交的变更。在提交修改后,暂存区会被清空。

分支是 Git 中的重要功能,正是分支让 Git 成为了 分布式 的版本控制系统

远程仓库存储公共的分支,而每个协作者只在自己本地私有的分支上修改。这使得协作者只需关注并同步的特定公共分支,而不需要关注其它分支,大大降低了沟通成本

在实现新功能之前最好本地新建一个分支,推送到远程仓库时也应该推送到指定的分支。项目通常会有一个主分支,主分支的代码应保证始终可用。远程仓库中可以启用主分支保护以避免被推送更新,然后由高权限的人员定期将别的分支合并到主分支中

钩子指一段自动触发的脚本。Git 提供了钩子机制,可以在其执行某个操作前/后自动运行一段脚本。

Git 中有很多种钩子,可以修改它们来实现想要的功能。更多介绍请阅读 官方文档

  • pre-commit 提交前钩子,会在 git commit 前自动运行
  • post-commit 提交后钩子,会在 git commit 后自动运行
  • commit-msg 提交消息钩子,由 git commit/git merge 调用。这个钩子可以直接编辑消息文件来规范提交消息,还可以在检查消息文件后拒绝提交
  • post-checkout 检出后钩子,会在 git checkout/git switch 后自动运行
  • pre-rebase 变基前钩子,会在 git rebase 前自动运行
  • post-merge 合并后钩子,会在 git merge/git pull 后自动运行
  • pre-push 推送前钩子,会在 git push 前自动运行

对于常用的提交前钩子,社区维护了一个框架,可以在 prek 章节中了解更多。

Terminal window
# Windows
scoop install git
# Ubuntu
apt install git
# Arch
pacman -S git
# macOS
brew install git

Git 有非常多的子命令,详细的使用说明可以参考 官方文档

对于本地使用,基本的工作流是这样的

graph
    初始化 --> 修改 --> 暂存 --> 提交
Terminal window
# 初始化本地仓库
git init
# 暂存所有修改
git add --all
# 提交修改
git commit -m "Message"

若要远程协作,则工作流会更复杂一点

graph
    克隆 --> 新建分支 --> 拉取 --> 修改 --> 暂存 --> 提交 --> 推送
Terminal window
# 将远程仓库克隆到本地
git clone <url>
# 创建并切换到新分支
git branch <name>
git switch <name>
# 从远程仓库指定分支中拉取修改到本地仓库的对应分支
git pull <repository> <branch>
# 将本地仓库指定分支的修改推送到远程仓库的对应分支
git push <repository> <branch>

Git 使用配置文件,默认位置为 ~/.gitconfig。可以直接修改文件,也可以使用 CLI 进行配置

一些常用的配置如下

Terminal window
# 邮箱和姓名
git config --global user.name xxx
git config --global user.email xxx@example.com
# 关闭非 ASCII 字符转义
git config --global core.quotepath false
# 修改默认编辑器
git config --global core.editor 'code --wait'

分支可以创建、切换、删除

Terminal window
# 创建分支
git branch <name>
# 切换分支
git switch <name>
# 删除分支
git branch --delete <name>
# 列出所有分支
git branch --all

分支合并时如果发生冲突通常需要手动解决

Terminal window
# 将指定分支合并到当前分支
git merge <name>

工作目录中的修改可以保存到暂存区里,也可以移除出暂存区

Terminal window
# 查看工作目录和暂存区的状态
git status
# 将修改保存到暂存区
git add <file> # 单个文件
git add . # 整个目录
# 将修改移除出暂存区
git restore --staged <file>
git restore --staged :/

暂存区可作为一个临时的备份,可以查看未暂存或已暂存的更改。也可以随时撤销未暂存的更改,恢复成已暂存的状态

Terminal window
# 显示未暂存的更改
git diff <file> # 单个文件
git diff # 整个暂存区
# 显示已暂存的更改
git diff --staged <file> # 单个文件
git diff --staged # 整个暂存区
# 撤销对单个文件的未暂存更改
git restore path/to/file
# 撤销所有的未暂存更改
git restore :/

Git 的提交历史记录了所有的变更。可以查看、比较这些变更

Terminal window
# 查看完整的提交记录
git log
# 查看提交的详细信息
git show # 最新提交
git show HEAD~3 # 前三个提交
# 比较两次提交之间的差异
git diff <commit-hash> <commit-hash>

Git 的提交历史可以回溯和修改

Terminal window
# 回溯到之前的某个提交
git reset HEAD~1 # 前一个提交
git reset <commit-hash> # 指定的提交
# 交互式地修改某个提交之后的提交历史
git rebase -i HEAD~5 # 前五个提交
git rebase -i <commit-hash> # 指定的提交
git rebase -i --root # 初始提交

可以把当前提交作为补丁添加到之前的某个提交上

Terminal window
git commit --fixup=<commit-hash>
git rebase -i --autosquash <commit-hash>^

rebase 编辑器打开时,Git 会自动把 fixup! 提交移动到目标提交之后,并标记为 fixup。确认没问题后直接保存退出即可

VSCode 有更易阅读 diff 视图。由于 VSCode 提供了命令行工具 code,因此可以修改 ~/.gitconfig 让 git 能够使用 VSCode 的 diff

[diff]
tool = vscode
[difftool "vscode"]
cmd = code --wait --diff \"$LOCAL\" \"$REMOTE\"
[difftool]
prompt = false

之后就可以使用 git difftool 打开 VSCode 的预览视图来查看差异。而 git difftool 的用法和 git diff 完全相同

Terminal window
git difftool <commit-hash> <commit-hash>

git 可以给远程仓库设置别名。运行 git clone 后远程仓库会拥有默认的别名 origin。可以添加别的远程仓库以及对应的别名,也可以修改已有仓库的别名

Terminal window
# 查看所有远程仓库的地址和别名
git remote -v
# 添加远程仓库
git remote add <name> <url>
# 修改远程仓库的别名
git remote rename <old-name> <new-name>

在 GitHub 等平台协作时,通常习惯把自己 fork 的仓库设为 origin,而原仓库设为 upstream。如果此前已经 clone 了原仓库,那么可以修改原仓库的别名,然后添加自己的仓库

Terminal window
# 把原来的 origin 重命名为 upstream
git remote rename origin upstream
# 把自己的仓库设为 origin
git remote add origin <url>

推送和拉取的时候,可以直接使用别名,而不必填写完整的 url

Terminal window
# 把 origin 仓库 main 分支的修改拉取到本地仓库
git pull origin main
# 把本地仓库 main 分支的修改推送到 origin 仓库
git push origin main

可以修改仓库中的 git 配置,让 git pullgit push 使用默认的仓库和分支

Terminal window
# 默认为 origin 仓库的 main 分支和本地仓库的 main 分支
git branch --set-upstream-to=origin/main main

git pull 会把远程仓库中的修改合并到本地仓库的对应分支中。但有些时候我们只想知道远程仓库的状态,不想修改本地分支,这时就需要远程跟踪分支:把远程仓库的状态缓存到本地仓库中

Terminal window
# 更新对应远程仓库的跟踪分支
git fetch <repository>
# 列出所有跟踪分支
git branch --remotes

然后我们可以手动把修改合并到本地分支中

Terminal window
# 把 upstream 的 main 分支合并到本地的 main 分支
git switch main
git merge upstream/main
# 或者使用 rebase
git rebase upstream/main

远程跟踪分支毕竟只是缓存,而缓存就总会有失效的时候——比如本地仓库中还有对应分支的跟踪,但这个分支已经在远程仓库中删除了。此时可以用多种方式清理跟踪分支

Terminal window
# 更新后自动清理失效的跟踪分支
git fetch --prune <repository>
# 只清理失效的跟踪分支,不更新跟踪分支
git remote prune <repository>

也可以修改 git 配置,让 git fetch 会默认清理失效的跟踪分支

Terminal window
git config --global fetch.prune true