Git工作流
关于工作流,我会以GitHub为例,只介绍一种最常用、最好用的使用git工作的方法,这也是我工作中甚至是很多大厂程序员的方法。
全流程演示
先看一遍动画,再读下面每一步的解释。三个区域分别是 GitHub 远端、本地 git 仓库和磁盘上的工作区,蓝色是这一步发生变化的地方。
初始化
我们假设在GitHub上有一个仓库,也就是repository,它可以是别人的,也可以是自己的。
这个仓库的主分支叫做main,之前叫master。
假设这个main分支有一个commit是init。我们一般吧这个所有人都共享的远端的代码仓库,也就是GitHub,叫做remote。
当想要修改或者贡献代码的时候,第一件事就是要把remote的仓库复制到本地,也就是clone,例如:
git clone https://github.com/mem0ai/mem0.git
这样会在本地复制一个一模一样的仓库,注意,这里的本地最好想象成两个部分,这样比较好理解:
第一个就是本地的git仓库,这里面有所有你告诉git的信息。
另一个部分叫做磁盘,这个部分是我们的源文件真正在磁盘的样子。也就是通过Cursor这种编辑器打开时的状态。
在我们刚进行clone操作后,这三个部分都是一样的。
修改代码
1.建立一个分支
通过建立分支,而不是直接往主分支推送代码有很多好处,例如它不会把你的主分支搞的无法工作,并且很适合多人协作,这是使用git最正确的方式。
建立的方法很简单,就是在你克隆仓库后,使用如下命令,其中my-feature就是你的分支名字:
git checkout -b my-feature
这个命令会复制你当前的分支到你的新分支上,在我们克隆后,当前的branch或者说checkout是main,所以我们相当于复制了一份主分支到自己的新分支上。
于是我们的本地git中就有了main branch和my-feature branch。
2.查看代码变化
当你改好代码后,你的磁盘上面的文件是有变化的,但是git对此并不知道,这时候可以使用:
git diff
来查看硬盘的改变和git保存的分支有什么区别。一般建议,在做下一步的操作之前,先用该命令看一下到底做了什么改变。
3.提交到本地git
当我们决定把修改的文件告诉git的时候,可以使用:
git add <changed_file>
这个命令会把这些文件放到一个叫做暂存区的地方,git现在知道了你有一些代码想要commit。
在你添加了所有想修改的文件之后,可以使用:
git commit
把这些修改真正放到git里,使用后我们的本地git就会新增一个commit记录。这时候我们的本地分支已经和本地主分支不一样了。
4.提交分支到GitHub
到现在为止,远端仓库GitHub什么都不知道,我们需要把这个本地git仓库的变化告诉GitHub,这时候可以使用:
git push origin my-feature
使用这个命令后,会发现GitHub上多出来了一个branch,也就是my-feature,这个分支保存着你的代码改动。
5.同步本地主分支
比较常见的情况是,在我们修改代码的时候,主分支又有更新了,比如说我们push到my-feature这个分支后,主分支又多了一个新提交update。
这时候需要测试一下我们的分支在新的提交下是否能正常运行,所以需要将主分支的更新同步到my-feature这个分支里。
当前我们本地的主分支和remote的主分支是不一样的,首先切换到主分支,通过:
git checkout main
这时候硬盘代码状态会回到初始化的状态,而不是修改后的状态,然后运行:
git pull origin main
这个意思就是将远端的代码同步到我本地的主分支上,这时候远端新的更改代码提交就会同步到我的本地git仓库和磁盘上。
6.合并到本地分支
这时候本地和远端就一样了,然后回到自己的分支上:
git checkout my-feature
当前代码是我们修改后的但是没有远端更新后的代码。为了同步主分支的代码改变,使用:
git rebase main
表示把我的修改先扔到一边,把主分支最新的修改拿过来,然后再最新的修改基础之上,再把我的提交尝试弄回去。
这个过程中可能会遇到rebase conflict,如果出现了冲突,就需要手动选择到底要哪一段的代码。
rebase成功后,相当于在最新的主分支上面做了修改,这里是使用rebase而不是merge的好处。
7.推送到远端分支
更新完分支后,需要推送到远端分支:
git push -f origin my-feature
注意:由于我们做了rebase所以在推送到时候,必须加上-f,表示force,强行推送。
8.提出PR
现在一切准备就绪,我们需要把更新的代码合并到主分支上了,这个过程叫做pull request。
我们形式上认为,这个主分支是属于项目的,不属于任何个人,而功能分支,也就是feature branch是属于个人的,尽管他们可能是同一个人,但在两个分支上他们是不同的角色。
这个pull request的意思就是请求这个项目的主人把新的分支的改变,拉取到这个项目中去。
在GitHub上可以非常方便的建立PR,在这个项目维护者审查了你的代码后,一般情况下会用squash and merge。
在例子中我们分支只有一个提交,但是实际上功能分支的提交是比较杂乱的,我们不希望把这些提交每一个都放到主分支里面。
我们希望这个主分支提交历史尽可能的简洁,尤其是每一个提交都是能够正常工作的,所以我们会选择用squash and merge。
意思就是把一个分支上的所有改变合并成一个改变,然后把这个提交放到主分支上,也许叫update2,代码改动会被正常合并到主分支,只是提交到数量结构变化了。
有些大厂中,在提PR前会要求使用rebase -i命令在本地压缩提交到一个。
在PR被合并后,一般情况下会把远端的分支直接删掉,GitHub有一个按钮delete branch。
远端git删掉后,本地的git还有,这时候要切换到主分支,然后使用:
git branch -D my-feature
从本地git里删除分支,最后在主分支上再使用一次拉取命令,把最新更新拉取到本地主分支和磁盘上,经过这些操作后,我们本地又和远端同步了。
生产案例
实际生产中有的公司远端仓库会有两个分支,一个main分支,一个release分支,每一个release版本对应一个分支。
新的代码只往该代码的目标版本提PR,比如功能或者bug修复的目标版本是0.1.5,那么feature或bug分支就从0.1.5版本fork出来然后PR回0.1.5即可。
新的分支一般会从上一个已经发布的release分支fork出来,例如0.1.5就是release的0.1.4发布之后fork出来的。
main分支存在的意义就是对齐最新的release代码,比如0.1.4发布后会merge回main分支。
如果有两个release版本同时开发,这时候只需要PR回目标release分支。
老版本比如0.1.4发布后发现了bug,但是不想等到0.1.5修复,可以直接在0.1.4修补后重新部署,然后记得把修复后的0.1.4的release分支merge到0.1.5即可。