一个有趣的 Git 练习网站
前一阵子整理一个 GitHub 项目,整得焦头烂额。
一个项目我只上传了一部分文件上去,等到想要上传剩下的部分时,却告诉我无法完成提交。
因为对 Git 的操作还不是特别熟练,所以我最后只能采用了一个笨方法:下载 GitHub Destop 拖拽式上传。
为了以后不再出现这种尴尬的事情,我决定好好复习 Git 知识。
于是,我就找到了这个有趣的 Git 练习网站:Learn Git Branching。
- Git For Branching 界面。还是非常美观的。
经过学习后,我发现我的问题可能是 2.8 节所指出的,远程服务器的拒绝。
以下是我学习过程整理的记录。
1. 主要内容
1.1 Git 的思路
Git Commit 的提交记录保存的是文件快照,类似复制粘贴。
通过 diffing 算法,将当前版本与上个版本比较,并将差异保存起来作为提交记录。
Git 保存了提交历史记录。
// 提交
git commit
1.2 Git Branch
Git Branch 的分支思路也非常轻量化,仅仅指向某个提交记录。
建议早建分支,多建分支。
建完分支后,要切换分支。可以用 checkout 命令。当然,checkout -b 可以创建的同时切换。
// 创建分支
git branch newImage
git commit
// 切换分支
git checkout newImage
git commit
// 创建并切换分支
git checkout -b bugFix
1.3 Git Merge
Git Merge 用于合并两个分支,会产生一个特殊节点,包含两个父节点。
// 当前分支与目标分支合并
git merge bugFix
git checkout bugFix
git merge main
// 由于 main 的一个父节点是 bugFix
// 换句话说,main 继承自 bugFix
// 所以 Git 什么也没做
1.4 Git Rebase
Git Rebase 也是一种合并分支的方法。取出一系列提交记录,复制并放在另一个地方。
// 合并分支,得到线性提交序列
git rebase main
git checkout main
git rebase bugFix
// 由于bugFix 继承自 main,所以 Git 只是简单前移了 main
1.5 如何在提交树上移动
HEAD:你目前正在工作的提交记录,一般指向当前分支的最近一次提交记录。
查看 HEAD 指向可以用 cat .git/HEAD。
// 调整 HEAD 指向具体的提交记录,而不是分支
git checkout C1
1.6 相对引用
通过指定提交记录的哈希值移动 HEAD 不是很方便,因为哈希值都很长。
可以用 Log 查看提交记录的哈希值。
相对引用就是可以从一个容易记忆的地方移动 HEAD。^
向上移动一个提交记录如main^
,~<num>
向上移动多个提交记录,如~3
。
也可以用 HEAD 作为相对引用的参照。
使用相对引用最多的是移动分支,可以在 Branch 上加一个 -f 改变分支指向的提交节点。
// 查看日志
git log
// 找到 main 的父节点
git checkout main^
// 找到 HEAD 的父节点
git checkout HEAD^
// 让 main 分支指向 HEAD 的第三级父提交节点
// 可以说,git branch -f 和 git branch 内容都不一样
// git branch 是创建分支,而 git branch -f 是改变分支指向提交节点
git branch -f main HEAD~3
// 注意,不能写 git branch -f HEAD HEAD^,因为没有这个分支
// 只能用 checkout 调整 HEAD
git checkout HEAD^
1.7 撤销变更
可以通过 Reset 和 Revert 撤销变更。
Reset 相当于撤销历史。通过它可以回退本地的提交节点。但是无法处理远程分支。
Revert 用于撤销远程提交。这个过程引入了新的提交节点,因为这个节点做的事情是撤销上个提交节点的内容,所以和上上个节点的内容是一样的。
// 回退本地提交。只针对 local。
git reset HEAD~1
// 撤销远程提交。只针对 pushed。
git revert HEAD
1.8 整理提交记录
整理提交记录在处理复杂请求时十分重要。
Cherry-pick 可以用于将指定提交节点复制到 HEAD 分支上。
也可以用交互式 Rebase,也就是带--interactive
的 Rebase,简写为-i
。会用一个交互式 vim 进行排序和复制。将指定数量的内容按照指定顺序放在 HEAD 分支上。
// 将 C2 和 C4 复制到当前分支
git cherry-pick C2 C4
// 交互式 vim 进行复制
git rebase -i HEAD~4
1.9 本地栈式提交
git checkout c1
git cherry-pick c4
1.10 提交技巧
如果相对之前的提交节点进行调整,可以用 Rebase -i 调整位置,将想要修改的记录挪到前面,然后用 Commit --amend 修改,最后用 Rebase -i 调整回原位,最后将 main 移动到修改的最前端即可。
如果用 Cherry-pick 就更简单。
git rebase -i HEAD~2
git commit --amend
git rebase -i HEAD~2
// 下面这条命令,不仅把 main 挪到了最前面,还将 HEAD 指向了 main,完美
git branch -f main caption
git checkout main
git cherry-pick newImage
git commit --amend
git cherry-pick caption
1.11 Tag
Tag 可以用于永久指向某一个提交节点。
git tag v0 c1
git tag v1 c2
git checkout v1
1.12 Describe
Describe 用于找到离当前分支最近的锚点
(也就是 Tag 指向的点)
默认的参数是 HEAD
git describe
1.13 多分支 Rebase
git rebase bugFix
git rebase side
git rebase another
git rebase -i HEAD~7
1.14 选择父提交记录:
操作符^
后面也可以接数字,但是并不是选择返回第几代,而是选择合并提交记录的某个父提交。默认是第一个,数字可以改变。
git checkout HEAD~^2~2
git branch -f bugWork HEAD~^2^
1.15 纠缠不清的分支
2. 远程 Git 仓库
2.1 Clone
Clone 用于连接远程仓库,并获取一个远程分支 origin/main。
2.2 远程仓库
远程分支命名都是<remote name>/<branch name>
,例如origin/main
,则远程仓库是origin
,分支是main
。
远程分支和远程仓库相应的分支同步,而不会受到本地的 Commit 的影响。
git checkout origin/main
git commit
// 提交后,HEAD 与分支进行了分离
git commit
git checkout o/main
git commit
2.3 Fetch
Fetch 用于从远程仓库获取数据。下载缺失的提交记录,并且更新远程分支指针 origin/main。
远程分支反映了远程仓库在你最后一次和它通信时的状态,而 Fetch 就是和远程仓库通信方式。通常通过 http:// 或 git:// 协议与远程仓库通信。
注意,Fetch 不会改变本地仓库状态,不会更新 main 分支,也不会修改磁盘文件!!并不是 Fetch 后本地仓库就和远程仓库同步了!!Fetch 可以理解为单纯的下载操作。
// 下载本地仓库缺失的提交记录,并更新远程分支指针,如 origin/main
git fetch
2.4 Pull
当远程分支有新的提交时,可以像合并本地分支一样来合并远程分支。如执行下列命令。
git cherry-pick origin/main
git rebase origin/main
git merge origin/main
先抓取更新,再合并到本地分支,这个流程十分常见。通过 Pull 可以完成这个流程。
Pull 相当于 Fetch 和 Merge 的结合。
// 下载更新,并合并本地分支与 origin/main 分支
git fetch
git merge origin/main
// 执行一样的操作
git pull
2.5 模拟团队合作
git clone
git fakeTeamwork 2
git commit
git pull
2.6 Push
Push 将 HEAD 指向的分支上传到指定远程仓库,并再远程仓库上合并新提交记录。Push 不带参数时的行为和 push.default 的配置有关。
// 上传本地提交节点,更新远程节点和远程连接
git push
2.7 偏离的工作
Pull 和 Push 看起来没什么难度,但却非常让人困惑,这是源自于远程库提交历史的偏离。当本地提交不是基于远程最新提交节点时,Git 会强制要求本地先合并远程最新提交节点,然后再提交。这也是唯一的办法。
解决方法可以是 Fetch 和 Rebase,然后再 Push。用 Fetch 配合 Merge 也可以。
最简单的是 Pull --rebase,这是 Fetch 和 Rebase 的简写。
// 首先下载远程节点,然后合并最新节点,然后再提交,就不会有问题
git fetch
git rebase origin/main
git push
// 用 merge 也是一样
git fetch
git merge origin/main
git push
// 用 Pull 是最简单的
git pull --rebase
git push
// 相同的效果
git pull
git push
git clone
git fakeTeamwork 1
git commit
// 为什么这里用 pull --rebase 而不用 pull?因为 pull 产生的是新提交记录
// 而 pull --rebase 是将原先的提交记录复制下来
git pull --rebase
git push
2.8 远程服务器的拒绝
有时候 main 被锁定了,就需要 Pull Request 流程来合并修改。如果直接 Push,就会收到警告。所以不能直接提交 main 分支,而是应该按照流程,新建一个分支,Push 该分支,并申请 Pull Request。如果忘记并直接提交给 main,就会卡住,且无法推送更新。
解决方法是新建一个分支,推送到远程服务器,然后 Reset 本地 main 分支,和远程服务器保持一致。否则下次 Pull,并且他人提交和你冲突时就会有问题。
git clone
// 创建并切换到分支 feature
git checkout -b feature
// 提交分支
git push
// 回撤
git branch -f main HEAD^
2.9 推送主分支
// 将工作 rebase 到远程分支的最新提交记录
git pull --rebase
// 向远程仓库推送工作
git push
// 将 side1, side2, side3 放在一条分支上
git rebase side1 side2
git rebase side2 side3
// 将 main 分支放到头部,并将 HEAD 移到 main 分支上
git branch -f main HEAD
git checkout main
// 下载远程分支节点并提交
git pull --rebase
git push
2.10 合并远程仓库
为什么操作远程分支不喜欢用 Merge?Rebase 使提交树变得干净,所有提交都在一条线上。但是 Rebase 修改了提交树的历史。
// 首先切换回 main,然后拉取远程仓库提交节点,保持最新
git checkout main
git pull
// 合并所有本地分支
git merge side1
git merge side2
git merge side3
// 提交
git push
2.11 远程追踪
main 和 origin/main 是关联的。Pull 时,记录会被先下载到 origin/main 上,然后再和本地的 main 合并。Push 时,工作从本地的 main 推送到远程仓库的 main 分支,并同时更新 origin/main。因为 main 分支制定了推送的目的地和拉取的合并目标。这是 Git 提供的属性。
可以类似的设置这种远程追踪属性。Checkout -b 或者 Branch -u。
通过这种远程追踪命令,就可以不移动本地的 main,直接用其他分支替代 main 了!
// 第一种方法,创建新的分支,跟踪远程分支 origin/main,并获取 HEAD
git checkout -b totallyNotMain origin/main
// 第二种方法,命令分支跟踪远程分支。如果在 foo 分支上还可以省略 foo
git branch -u origin/main foo
git checkout -b side o/main
git commit
git pull --rebase
git push
2.12 Push 的参数
前面的远程追踪提到了,Push 未指定参数时是根据当前分支的属性确定远程仓库和要 Push 的目的地的。
指定参数中,包括<remote>
和<place>
。
当<place>
参数为 main 时,同时指定了提交记录的来源和去向都是 main。如果想让来源和去向不一样,如本地的 foo 推送到远程的 bar,也是可以的。中间加一个冒号就可以了,<source>:<destination>
。
如果<destination>
不存在,远程仓库会自己创建这个分支。
// 切换到本地仓库的 main 分支,获取所有提交
// 然后到远程仓库 origin 找到 main 分支,将远程仓库没有的记录都添加上去
git push origin main
// 向远程仓库 origin 分别提交 foo 和 main 分支
git push origin foo
git push origin main
// 向远程仓库 origin 的 main 分支分别提交本地的 foo 分支和 foo 分支的父节点
git push origin foo:main
git push origin foo^:main
git push origin main^:foo
git push origin foo:main
2.13 Fetch 参数
Fetch 的参数和 Push 是一样的!只不过方向反了而已。
// 从远程仓库 origin 的 foo 分支上,获取本地不存在的提交
// 放到本地的 origin/foo 上
git fetch origin foo
// 课后挑战
git fetch origin foo:main
git fetch origin main^:foo
git checkout foo
git merge main
2.14 调整 <source>
在 Push 和 Fetch 的时候,可以将冒号前面的<source>
留空。Push 的效果是删除分支!Fetch 的效果是创建新分支。
// 删除远程仓库的 foo 分支和本地的 origin/foo 分支
git push origin :foo
// 新建本地的 bar 分支
git fetch origin :bar
2.15 Pull 的参数
Pull 就是 Fetch 和 Merge 的缩写。可以理解为用同样的参数执行 Fetch,然后再 Merge 所抓取到的提交记录。Merge 的对象是 HEAD 指向的提交节点或分支的节点。注意 Merge 之后的节点还是 HEAD 指向的分支。
git pull origin bar:foo
git pull origin main:side
通关啦~~~
3. 其他 Git 操作
3.1 初始化
Init 可以进行初始化。
git init
3.2 添加文件
Add 可以添加文件。可以反复添加文件。
// 上传 foo 文件到本地仓库临时区
git add foo
3.3 查看工作区
Status 可以用于查看工作区状态。
// 查看工作区状态
git status
3.4 查看修改内容
Diff 可以查看修改内容。
// 查看修改内容
git diff
3.5 查看命令历史
Reflog 可以查看命令历史。
// 查看命令历史
git reflog
Comments | 1 条评论
我最近在学习Git,感谢博主推荐,知乎来的。