Git 随记
警告:本文使用机翻且尚未经过审核,可能包含拼写错误或概念性错误。
本文假设读者理解基本的 Git 操作,包括 git init/clone/status/add/rm/stash/log
,git reset
最好了解。
参考教程:
新建 repository 🥶
可以通过 git init
或 git clone <git-url>
创建仓库。
例如,如果通过从 GitHub 克隆创建了本地 Git 仓库,那么 git remote --verbose
将显示类似以下内容:
(HTTPS)
➜ git remote -v
origin https://<url>/<owner>/<repo> (fetch)
origin https://<url>/<owner>/<repo> (push)
(SSH)
➜ git remote -v
origin git@<url>:<owner>/<repo>.git (fetch)
origin git@<url>:<owner>/<repo>.git (push)
(派生(fork)仓库)
➜ git remote -v
origin git@<url>:<fork-owner>/<repo>.git (fetch)
origin git@<url>:<fork-owner>/<repo>.git (push)
upstream git@<url>:<owner>/<repo>.git (fetch)
upstream git@<url>:<owner>/<repo>.git (push)
如何手动配置远程仓库?
假设我们有一个含有分支 master
的仓库,并且记录了远程仓库 origin
。
git push -u origin master
此命令将本地分支 master
推送到远程分支 origin/master
,并记录上游跟踪 master -> origin/master
(可以通过 git status
检查跟踪状态)。
分支/仓库名称没有特殊含义。一般按照约定采用默认分支名称 master
main
,开发分支 dev
;远程主仓库名称 upstream
,个人仓库名称 origin
。
以下是一个工作流程示例。(偷自 nixpkgs/CONTRIBUTING.md)
# 添加一个名为 `upstream` 的远程仓库跟踪
git remote add upstream <git-url>
# 确保你有来自 `upstream` 的最新更改
# 附加 `--depth 1` 如果只需要 `upstream` 的最新提交
git fetch upstream
# 基于 `upstream` 的 master 分支创建并切换到一个新分支 `update-hello`
# 等同于 `git checkout --branch/-b`
git switch -c update-hello upstream/master
# 或基于特定提交
git switch -c update-hello <the desired base commit>
然后可以开始在这个分支上添加你的更改到暂存区,并通过 git commit
保存。
# 如果设置了 `EDITOR` 环境变量,这将触发相应的编辑器可执行文件并显示当前更改状态。
# 然后你应该在编辑器中编辑提交消息。
# 附加 `--message/-m "<提交消息>"` 可以直接提交更改而不打开编辑器
git commit
合并分支 / 解决合并冲突 / 拉取请求
TODO
标签
参考 https://git-scm.com/book/en/v2/Git-Basics-Tagging
# Lightweight tag(不含说明信息)
git tag <tag> <commit>
# Annotated tag(类似 `git commit` 编辑标签说明)
git tag -a <tag>
# 或者直接附加标签说明
git tag -a <tag> -m "<message>"
# 默认下 `git push` 不会推送标签到远程仓库
# 推送指定标签
git push <remote-repo> <tag>
# 推送全部标签
git push <remote-repo> --tags
重写历史
重写历史过程中可以使用 git stash
贮藏当前工作区状态,使用非常简单,请自行 RTFM。
重写当前提交:
# 重写当前提交(假设你不在 HEAD 分离模式下)。
# 如果你不想编辑提交消息,请附加 `--no-edit`。
git commit --amend
如果你想重写多个提交或远离 HEAD 的提交,可以使用交互式变基 (git rebase --interactive/-i
)。这是一个强大的工具,允许你修改历史中的多个提交。以下是具体步骤:
# 在历史中交互式地变基到某个提交
# 这应该是你想要编辑的最早提交的**父**提交。
# 使用 <TAB> 按钮来利用 shell 补全。
git rebase --interactive <commit>
# 重写包括根提交的全部历史
git rebase -i --root
eg.
git rebase -i HEAD~4
这将打开一个编辑器,显示类似以下内容:
pick f7f3f6d 修改配置文件
pick 310154e 更新用户界面
pick a5f4a0d 修复bug
pick 368fab6 添加新功能
# 变基 710f0f8..a5f4a0d 到 710f0f8
#
# 命令:
# p, pick <提交> = 使用提交
# r, reword <提交> = 使用提交,但修改提交信息
# e, edit <提交> = 使用提交,但停止以便修改
# s, squash <提交> = 使用提交,但融合到前一个提交
# f, fixup <提交> = 类似于 "squash",但丢弃提交信息
# x, exec <命令> = 使用 shell 运行命令(此行剩余部分)
# b, break = 在此处停止(使用 'git rebase --continue' 继续变基)
# d, drop <提交> = 删除提交
# l, label <label> = 为当前 HEAD 打上标记
# t, reset <label> = 重置 HEAD 到该标记
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . 创建一个合并提交,使用原始的合并提交信息(如果没有指定
# . 原始提交,使用 oneline 作为提交信息)
#
# 可以对这些行重新排序;它们会从上至下被执行。
#
# 如果你在这里删除一行,对应的提交将会丢失。
#
# 然而,如果你删除全部内容,变基操作将会终止。
#
如果要修改提交内容,将想要修改的提交前的 "pick" 改为 "edit"(或简写 "e")。例如:
edit f7f3f6d 修改配置文件
edit 310154e 更新用户界面
edit a5f4a0d 修复bug
pick 368fab6 添加新功能
然后 Git 将变基到第一个要修改的提交。
完成后,运行 git rebase --continue
,Git 将应用所有更改,重写历史并变基到下一个要修改的提交。
你可以通过 git rebase --skip
跳过更改,或通过 git rebase --abort
退出变基模式。
注意:这个过程会改变提交的 SHA-1 值,因此要谨慎使用,特别是在已经推送到共享仓库的提交上。使用前确保与其他人沟通,并使用 git push --force-with-lease
推送。
切忌无脑 git push --force/-f
!!!!!!(伤身而且影响你的仓库!这样会让你一年一篇顶会都没有!)
补充阅读:Is git push --force-with-lease always safe?
Git 提交的不可变性(immutable) / 重写历史的基本原理
在 Git 版本控制中,即使重写历史的相关操作看起来修改了一些提交,其实并不是覆写了这些提交,而是创建了新的提交替换到树上。
Git 中的每个提交(commit)都是不可变的(immutable),每个提交在创建时,根据提交时间/内容等属性,计算并记录不可变的散列值(hash value),作为唯一标识(补充:hash 前缀索引)。 当我们执行 git rebase、git commit --amend 等"重写历史"的操作时,实际上是创建了新的提交对象,这些新提交有: 新的散列值 新的提交时间 可能有新的父提交关系
eg.
原始提交
A -> B -> C (master)
执行 git commit --amend
修改最后一个提交
A -> B -> C' (master)
\-> C (原提交仍然存在,但不可见)
保证数据完整性和安全性
便于分布式协作
支持版本回溯
防止历史被真正篡改
这就是为什么在进行这类操作时,Git 会警告我们要小心,特别是在已经推送到远程仓库的提交上执行这些操作时,因为这实际上会创建不同的提交历史线。
git diff - Myers 差分算法 / diff3 OR zdiff3
TODO
更多
Pro Git 书籍 高级 Git 用法