Git入门
git入门之learn_git_branching
网站Learn Git Branching的学习笔记,常用的主要就是基础和远程运用部分
主要原理
基础
1. git commit
Git 仓库中的提交记录保存的是你的目录下所有文件的快照,就像是把整个目录复制,然后再粘贴一样,但比复制粘贴优雅许多!
Git 希望提交记录尽可能地轻量,每次进行提交时,它并不会盲目地复制整个目录。条件允许的情况下,它会将当前版本与仓库中的上一个版本进行对比,并把所有的差异打包到一起作为一个提交记录。通常是将所需要提交的部分用add加入进暂存区,之后用commit进行提交。我们可以把一个提交记录视作为git运行的一个”单位“或者”节点“,每次commit都会产生一个新节点。
Git 还保存了提交的历史记录。大多数提交记录的上面都有 parent 节点 。
2. branch
Git 分支实际上是指向更改快照的指针,不占存储空间。
-
创建分支
用
git branch <分支名>来创建分支,用git checkout <分支名>来切换到分支。git checkout -b <分支名>创建并切换分支。分支名实际上是个指针,指向最新的提交记录。 -
合并分支
- git merge 链接
原理:使用
git merge <分支名>来合并分支,git merge会将多个提交序列合并进一个统一的提交历史。当前工作分支的内容会由于merge操作产生更新,但是目标分支则完全不受影响。git merge接受两个commit指针,通常是两个分支的顶部commit,然后向前追溯到这两个分支最近的一个共同提交。一旦找到这个共同提交,Git就会创建一个新的"merge commit"提交记录,用来合并两个分支上各自的提交序列,作为两个提交序列的子提交记录。通常会搭配
git checkout和git branch -d'进行所需分支的切换和删除。若合并的同一数据有不同的更改,则会发生冲突,需要人为更改例子:
- 创建并切换为bugFix分支(加*号为当前分支),此时两个分支指针指向同一个提交记录c1,使用
git merge不会更新

-
在bugFix下提交,新增了提交记录c2。
若此刻使用
git merge main,会发生什么?由于c2可以作为main和bugFix的子提交记录,所以不会产生新提交记录,同时bugFix作为当前工作分支,包含了main的所有提交记录(c0,c1),故不会产生更新。

但若此时将工作分支变为main进行
git merge bugFix的话,main分支就会获取到bugFix的提交记录,同时main分支和bugFix会指向子提交记录。由于c2可以作为main和bugFix的子提交记录,此时就发生了快进合并,在这种情况下,不需要真实的合并两个分支,Git只需要把当前分支main的顶端指针(即分支指针)移动到目标分支bugFix的顶端就可以了(也就是快进的意思)。
-
正常情况下,将工作分支变为main进行
git commit创建提交记录,再进行git merge bugFix,就会正常创建一个共同的子提交记录c4,指向工作分支和目标分支,同时main指针指向新记录c4。
使用命令如下:git checkout -b bugFix git commit git checkout main git commit git merge bugFix
-
-
git rebase <目标分支> <要移动的分支>
Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。
如果需要让
bugFix分支获取main的代码更新,使用rebase会把bugFix分支的起始历史放到main分支的最后一次提交之上,也达成了使用main分支中新代码的目的。但是,相对于merge操作中新建一个合并提交,rebase操作会通过为原始分支的每次提交创建全新的提交,从而重写原始分支的提交历史。git checkout -b bugFix git commit git checkout main git commit git checkout bugFix //或者直接git rebase main bugFix git rebase main
高级
1. HEAD
在 Git 中,每个分支都有一个指向最新提交的指针。当我们进行提交操作时,HEAD 会自动指向最新提交,并将分支的指针也更新到最新提交。换句话说,HEAD 在每次提交后都会随之移动。
-
分离HEAD
也就是说,HEAD通常指向分支指针,如上图的
bugFix(显示为bugFix*说明被HEAD指向),要分离的话可以用命令git checkout c2'直接指向提交记录。
2. 相对引用
但实际应用中没有像教程那样的节点树,所以你就不得不用 git log 来查查看提交记录的哈希值(一个提交记录对应一个哈希值,git中只需要哈希值中的前几个字符)。这样引用太过麻烦,可以使用^或者~num符号进行相对引用,表示让 Git 寻找指定提交记录的 parent 提交。
如下指令效果一样,使得HEAD指向c3:
git checkout bugFix^
git checkout bugFix~1
git checkout HEAD^

除了分离HEAD,利用相对引用还可以改变分支指针指向的提交记录。
通过如下指令,实现两个提交树的转换:
git log //查看哈希值,教程中哈希值即c6
git branch -f main c6
git checkout HEAD^
git branch -f bugFix HEAD^ //或者git branch -f bugFix bugFix~3


3. 撤销变更
-
- git reset [--soft | --mixed | --hard] [HEAD]
直接回滚到指定的
commit--mixed (默认) --soft --hard 文件不变,重置暂存区 文件不变,暂存区不变,可直接重新commit 撤销工作区中所有未提交的修改内容,将暂存区与工作区都回到上一次版本,并删除之前的所有信息提交 -
- git revert [HEAD]
提交一个新的commit,来撤销之前的commit。不会撤销工作区内容
使用revert可以使得远程分支保留撤销分支的操作,reset在push后撤销的提交记录消失
通过如下指令,实现两个提交树的转换:
git reset HEAD^ git checkout pushed git revert pushed- 其中
pushed分支是远程分支,local是本地分支


移动提交记录
1. git cherry-pick
将一些提交复制到当前所在的位置(HEAD)下面
2. rebase UI
使用rebase的时候使用选项 -i, 可以使用可交互界面进行提交记录移动
杂项
1. 只取一个提交记录
在对代码修改的时候通常会进行多次提交(如下)。最后bugFix为修改完完成的提交记录

而若只想让main合并最后一次提交而不会包含之前的提交记录,则不能使用快速合并如下

通过git cherry-pick bugFix指令或者用git rebase -i main,实现只合并最后一个提交记录:

2. 修改过去的提交记录
问题:你之前在 newImage 分支上进行了一次提交,然后又基于它创建了 caption 分支,然后又提交了一次。此时你想对某个以前的提交记录进行一些小小的调整。比如设计师想修改一下 newImage 中图片的分辨率,尽管那个提交记录并不是最新的了。
git commit --amend是一个强大的 Git 命令,用于修改最近一次提交的内容或提交信息,而无需创建新的提交记录。
通过如下指令实现修改过去的提交记录:
法1:
git rebase -i main //将c3 c2换个位置,让newImage变成最新的提交记录
git commit --amend //修改提交记录
git rebase -i main //将c3 c2位置换回来,让caption变成最新的提交记录
git rebase caption main
rebase交换顺序可能会引发错误
法2:
git checkout main
git cherry-pick c2
git commit --amend
git cherry-pick c3

3. git tag
使用git tag [标签名称] [HEAD] 为特定提交记录命名且不会因为commit操作改变所指向的提交记录。
可以视为某种程度上永远不会变的分支指针
4. git describe
git describe <ref>
<ref> 可以是任何能被 Git 识别成提交记录的引用,如果你没有指定的话,Git 会使用你目前所在的位置(HEAD)。
它输出的结果是这样的:
<tag>-<numCommits>-g<hash>
tag 表示的是离 ref 最近的标签, numCommits 是表示这个 ref 与 tag 相差有多少个提交记录, hash 表示的是你所给定的 ref 所表示的提交记录哈希值的前几位。
当 ref 提交记录上有某个标签时,则只输出标签名称。
如图:
git describe main 会输出:
v1-2-gC2 //v1是离main最近的标签,相差两个提交记录,hash为c2
git describe side 会输出:
v2-1-gC4

高级操作
多次rebase
原树

输入以下指令后
git rebase main bugFix //将c3 移到c2 下
git rebase side another //将c7 移到c6下
git rebase bugFix //将c7 的起始历史即c4 移到c3下
git rebase another main

两个parent
merge后,通常会产生两个parent的提交记录,这时候使用相对引用可以在^符号后添加数字来节点分离到哪个parent(Git 默认选择合并提交的“第一个” parent 提交)。同时相对引用支持链式操作。
使用git branch -f bugWork HEAD^^2^,在c2新建分支bugWork

纠缠不清的分支
使用如下指令实现树的转换
git checkout one
git cherry-pick c4 c3 c2
git checkout two
git cherry-pick c5 c4 c3 c2
git branch -f three c2

远程运用
push && pull
-
before:
对git进行配置——打开git bash 输入
git config --global user.name "名字" git config --global user.email "真实邮箱"查看配置
git config --global --list更实用性的博客(使用vs code) -->链接
1. git clone
github中找到需要的项目,在需要克隆的文件下打开git bash或cmd(环境变量得配好)。
通过 git clone [HTTPS链接]即可克隆下来

2. 远程分支
存储在远程仓库中的分支,用于与其他开发人员共享代码。
远程分支反映了远程仓库在你最后一次与它通信时的状态(用fetch通信的)
格式 <remote name>/<branch name>

如图所示蓝色即为本地分支紫色为远程分支。本地分支还未更新。
使用 git push -u origin <name>建立跟踪分支
3. git fetch
git fetch 就是你与远程仓库通信的方式
git fetch 完成了仅有的但是很重要的两步:
- 从远程仓库下载本地仓库中缺失的提交记录
- 更新远程分支指针(如
o/main)
git fetch 实际上将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态。
git fetch 并不会改变你本地仓库的状态。它不会更新你的 main 分支,也不会修改你磁盘上的文件。可以单纯理解为下载操作。

右边的树为远程仓库的数据,使用fetch即可拉取

第2部分的图中就是用了fetch与远程仓库进行交流,更新了远程分支,但本地分支未更新,需要使用merge进行更新。实际上,pull就是fetch加merge。

merge后本地分支便更新了。main指针和origin/main指针指向同一个提交记录。

4. git pull
相当于
git fetch origin main # 下载更新
git merge origin/main # 自动合并到当前分支

如图,git pull自动执行了fetch(拉取c3)和merge(合并c2 c3)操作。
- 注意到,git fetch拉取了全部远程更新,而pull 只获取指定分支的更新
5. git push
格式如下
git push <远程主机名> <本地分支名>:<远程分支名>
通常情况后面的参数可省略。
如果本地分支名和远程分支名一样的情况下,可以省略:<远程分支名>。如果远程主机中不存在该分支,那么会被创建。省略“本地分支名”需要执行2中的带参数-u的push指令建立跟踪关系。
- push成功后,远程分支的指针会自动定位到最新的提交记录
6. 冲突
若本地和远程代码不同,git pull和git push都会产生冲突。我在本地和远程分别在同一文件里commit两个不同的修改。
在本地git pull 产生冲突

reset一下然后在本地git push 无法push

需要做的就是使你的工作基于最新的远程分支。使用git pull/git pull --rebase 或者 git fetch后merge/rebase就行。
解决冲突后,需要进行commit。
当然,如果在你最后一次git pull或fetch与远程分支通信后远程分支没有变换,不会产生冲突,这时候直接push就行。
7. 新建新分支提交
如果你是在一个大的合作团队中工作, 很可能是main被锁定了, 需要一些Pull Request流程来合并修改。如果你直接提交(commit)到本地main, 然后试图推送(push)修改, 你将会收到这样类似的信息:
! [远程服务器拒绝] main -> main (TF402455: 不允许推送(push)这个分支; 你必须使用pull request来更新这个分支.)
新建一个分支feature, 推送到远程服务器. 然后reset你的main分支和远程服务器保持一致, 否则下次你pull并且他人的提交和你冲突的时候就会有问题。
通过以下指令实现:
git branch -f main o/main //不再使用main分支,确保本地 main 分支与远程完全同步,放弃任何本地提交
git checkout -b feature C2 //从代码历史的某个点(C2)开始新功能开发,而不是从最新代码开始,正常情况通过查reflog表的哈希值转移(log表查不到了)。
git push origin feature

Git 远程仓库高级操作
1. 推送主分支
- 实现如下分支推送:

法一:
git checkout main
git pull --rebase
git cherry-pick c2 c3 c4 c5 c6 c7
git push
法二:
git fetch //或者git pull不过需要先checkout到main分支,因为git fetch是获取全部分支,无需指定
git rebase o/main side1
git rebase side1 side2
git rebase side2 side3
git rebase side3 main //或者git branch -f main side3 后checkout 到main
快速前进...
git push
2. merge 方法
在开发社区里,有许多关于 merge 与 rebase 的讨论。以下是关于 rebase 的优缺点:
优点:
- Rebase 使你的提交树变得很干净, 所有的提交都在一条线上
缺点:
- Rebase 修改了提交树的历史
比如, 提交 C1 可以被 rebase 到 C3 之后。这看起来 C1 中的工作是在 C3 之后进行的,但实际上是在 C3 之前。
一些开发人员喜欢保留提交历史,因此更偏爱 merge。而其他人可能更喜欢干净的提交树,于是偏爱 rebase。仁者见仁,智者见智。
- 实现如下分支推送:

指令如下:
git checkout main
git pull
快速前进...
git merge side1
git merge side2
git merge side3
git push
3. 远程追踪
在前几节课程中有件事儿挺神奇的,Git 好像知道 main 与 o/main 是相关的。当然这些分支的名字是相似的,可能会让你觉得是依此将远程分支 main 和本地的 main 分支进行了关联。这种关联在以下两种情况下可以清楚地得到展示:
- pull 操作时, 提交记录会被先下载到 o/main 上,之后再合并到本地的 main 分支。隐含的合并目标由这个关联确定的。
- push 操作时, 我们把工作从
main推到远程仓库中的main分支(同时会更新远程分支o/main) 。这个推送的目的地也是由这种关联确定的!
main 和 o/main 的关联关系就是由分支的“remote tracking”属性决定的。main 被设定为跟踪 o/main —— 这意味着为 main 分支指定了推送的目的地以及拉取后合并的目标。
-
自定义分支跟踪:
- 第一种就是通过远程分支切换到一个新的分支,执行:
git checkout -b totallyNotMain o/main就可以创建一个名为
totallyNotMain的分支,它跟踪远程分支o/main。- 另一种设置远程追踪分支的方法就是使用:
git branch -u命令,执行:
git branch -u o/main foo这样
foo就会跟踪o/main了。如果当前就在 foo 分支上, 还可以省略 foo:git branch -u o/main- 还有一种就是小节
4. git push中的 -u 选项
通过如下指令实现通过其它分支推送:
git checkout -b side o/main
//local branch "side" set to track remote branch "o/main"
$ git commit
$ git pull --rebase
$ git push

4. push 参数
详细了解一下push参数。
我们可以为 push 指定参数,语法是:
git push <remote> <place>:<destination>
先看看例子:
git push origin main
把这个命令翻译过来就是:
切到本地仓库中的“main”分支,获取所有的提交,再到远程仓库*“origin”中找到“main”分支,将远程仓库中没有的提交记录都添加上去,搞定之后告诉我。*
我们通过“place”参数来告诉 Git 提交记录来自于 main, 要推送到远程仓库中的 main。它实际就是要同步的两个仓库的位置。
需要注意的是,因为我们通过指定参数告诉了 Git 所有它需要的信息, 所以它就忽略了我们所切换分支的属性!
<destination>指明了提交记录的去向。如果你要推送到的目的分支不存在,Git 会在远程仓库中根据你提供的名称帮你创建这个分支!
下面为例子:
git push origin foo:main
git push origin main^:foo

5. Git fetch 的参数
类似于
git push <remote> <source>:<destination>
如果你像如下命令这样为 git fetch 设置 的话:
git fetch origin foo
Git 会到远程仓库的 foo 分支上,然后获取所有本地不存在的提交,放到本地的 o/foo 上。
可以看出fetch冒号两边的参数对于push恰好是相反的。跟 git push 一样,如果<destination>不存在,Git 会在 fetch 前自己创建立本地分支。
下面为例子:
git fetch origin c3:foo //指定了放到本地的分支为foo,不指定的话由于本地没有c3这个远程分支,则会报错。如果是 main:foo ,o/main也会放到新拉取的分支
git fetch origin c6:main
git checkout foo
git merge main

6. <place>&<source>为空
git push origin :foo ——删除远程仓库的foo分支
git fetch origin :bar ——本地新建分支bar
7. git pull参数
git pull 实际上就是 fetch + merge 的缩写,所以它的参数与getch相同 ,git pull 唯一关注的是提交最终合并到哪里(也就是为 git fetch 所提供的 destination 参数)
最后的例子!:
//当前分支是main,pull后会对main合并
git pull origin c3:foo //远程分支不能用bar,不然o/bar也会放到c3提交记录
git pull origin c2:side

总结
理论与实际运用差别大,知行合一,需要多多实践

浙公网安备 33010602011771号