git教程:https://www.liaoxuefeng.com/wiki/896043488029600/896067008724000
下面的笔记只简单记录git的一些原理,不记录具体的命令,有需要的时候再网上查具体的执行命令。 (博客图片放在github图床上,网络能访问github的情况下才看得到图片哦)
git简介
git是一个分布式的版本控制系统,最初由Linux的作者Linus用两周时间用C语言开发出来,用于管理Linux源码,方便Linux社区的志愿者为Linux贡献代码。随后github网站上线,为所有开源项目免费提供git存储,因此一大堆开源项目逐步迁移到github中。
repository
repository即版本库/仓库,其实就是相当于一个被git管理的目录,改目录下的所有文件的新增、修改、删除等操作都会被git跟踪。
用git init 命令可以把一个目录变成Git可以管理的仓库,仓库下会生成一个git用于管理版本库的.git 目录,其中的内容不可以手动修改,否则会破坏git仓库。创建版本库时git也自动为我们创建了一个master 分支。
把文件添加到版本库
可以用git add和git commit命令把文件添加到版本库,其中git add命令实际上就是把要提交的所有修改放到暂存区(Stage),然后执行git commit就可以一次性把暂存区的所有修改提交到分支,后面可以随时回退到这个历史版本。
git跟踪并管理的是修改,而非文件。
注意git只能跟踪文本文件的变化,不能跟踪图片、视频这种二进制文件的内容变化,word文件也是二进制文件,因此也跟踪不了。
git版本
版本回退
每次commit都会产生一个历史版本,回退到之前某一个版本的方法是
git log
git reset --hard commit_id
工作区和暂存区
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cr34bm10-1639130586741)(https://raw.githubusercontent.com/zhongzhh8/Images/master/img/0.jpeg)]
我们在git目录下创建和修改文件时,都处于工作区,随后使用git add命令后,文件修改被添加到暂存区,随后可以用git commit命令将暂存区的修改提交到版本库中。注意git commit只会提交暂存区的修改,不会提交工作区的修改。
用git status命令可以看到,被git add到暂存区的文件的状态是modified,没有被git add到暂存区过的文件的状态是untraced。
git diff 看代码差异
- 查看工作区与暂存区的差异:
git diff <file> - 查看暂存区与上一次提交(commit)的差异:
git diff --staged <file> - 查看已缓存的与未缓存的所有改动:git diff HEAD
撤销修改
撤销修改可以分成三个场景
场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令
git checkout -- <file>
git checkout – 命令可以使工作区返回到上一次git add或git commit的状态(如果git add过就从暂存区恢复,如果没有git add过就从版本库中恢复)。注意删除文件也是一种修改,所以误删文件时也可以用git checkout命令恢复。
场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步
git reset HEAD <file>
git checkout -- <file>
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考上面版本回退的命令,不过前提是没有推送到远程库。
远程仓库
远程仓库其实就是一台机器充当服务器,所有开发者都从这个远程库里pull最新的代码,并且自己本地开发完成后,把代码push到远程库并且合并到主分支上,后续其他开发者再从主分支上拉取最新代码到本地继续开发。
对于公司而言,一般是有私有的gitlab仓库,对于个人开发者而言,大多会选用公有的github仓库,免费用户注册后就可以获得git远程仓库(但是会被公开给所有开发者)。
ssh key
由于本地git仓库和远程github仓库是通过SSH加密传输的,因此需要创建SSH key,
ssh-keygen -t rsa -C "youremail@example.com"
创建key时也不需要密码,创建完后在用户主目录里找到.ssh 目录,可以看到id_rsa 和id_rsa.pub 两个文件,它们就是SSH Key的秘钥对,id_rsa 是私钥,不能泄露,id_rsa.pub 是公钥,可以公开。然后登录github,在用户设置的SSH Keys处填入公钥id_rsa.pub 的内容就可以了。
ssh非对称加密验证原理
参考git的ssh key使用和原理
当本地主机需要登录远程主机时,本地主机向远程主机发送一个登录请求,远程收到消息后,随机生成一个字符串并用公钥加密,发回给本地。本地拿到该字符串,用存放在本地的私钥进行解密,再次发送到远程,远程比对该解密后的字符串与源字符串是否等同,如果等同则认证成功。
配了ssh key后才能实现push代码的时候不需要反复输入自己的github账号密码,更方便。当然换一台电脑再想push的时候,又要再配置一次ssh key才行,github支持添加多个ssh key,可以用多台不同的电脑push代码到远程库里。
添加远程库
在github上创建远程仓库后,它是空的,因此需要将一个本地仓库与之关联,然后将本地仓库的内容push到远程仓库上。
git remote add origin git@github.com:michaelliao/learngit.git
添加后,远程库的名字就叫做origin(git默认推荐名字),添加完远程库后就可以将本地库的内容推送上去
git push -u origin master
origin是什么意思
实际上也可以用其他名字来命名远程库,比如aMao,不过push的时候也需要用aMao来代表远程库
git remote add aMao git@github.com:michaelliao/learngit.git
git push -u aMao master
参考Git 里面的 origin 到底代表啥意思?
从远程库克隆
上面的方法是自己从本地开始建库,然后再创建远程库,并且做关联的,相当于我们是这个远程仓库的创始人了。但是我们加入开发团队时往往是已经存在远程仓库了,这个时候我们要做的是直接把远程库克隆下来,生成我们的本地仓库,然后再进行开发。
git clone + 仓库地址
git clone git@github.com:michaelliao/gitskills.git
分支管理
HEAD指向当前分支,分支指针指向提交。
创建和合并分支
一开始时只有master分支,master指针指向最新的提交。此时创建分支dev,则dev指针也指向当前的提交,可以看到master和dev还没有分家。此时再切换到dev分支上,则HEAD指向dev指针。
其中,创建分支和切换分支的命令分别是
git branch feature/zhanhzhong/new_branch
git checkout feature/zhanhzhong/new_branch
查看已有分支
git branch
在新分支上开发完成并且commit之后,教程就让把新分支合并到本地master上了,但是实际上合并到本地master没有太大意义,最终还是需要合并到远程master上,才能编译、部署到现网环境中(只支持编译master)。
所以现在直接看如何将分支合并到远程仓库的master上,首先需要pull远程master下来解决冲突(因为开发期间可能有其他开发者把他们的分支合并到远程master了)
git pull origin master
按照提示解决冲突后git add、git commit命令提交,再然后push到远程库
git push origin feature/zhanhzhong/new_branch
如果远程库里没有这个分支,这个命令会默认生成一个同名的分支,并且把本地分支push到这个远程分支上。
然后就在远程的git网页上提交merge请求,让别人code review,并且点approve之后,就可以合并到远程master上了。合并后是这样的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fbDxqxLQ-1639130586744)(https://raw.githubusercontent.com/zhongzhh8/Images/master/img/0-20211210171322875.png)]
删除分支
新建featue分支来开发新特性后,需要删除分支的场景主要可以分成两种:
1、feature已开发完成,并且已merge进master分支。
此时feature分支已经没有存在的意义了,即使后面再在它上面做新的修改,也无法合并进master中,所以完全可以删除此分支
git branch -d feature/zhanhzhong/dev
如果后面还要对这个新特性做修改,直接在master上新建一个分支即可,因为master上已经存在有之前开发的新特性了。
2、feature尚未开发完成,还没有merge进master分支。
这种情况一般是需求取消,或者技术方案有问题需要推倒重来。此时由于分支没有合并进master,所以删除分支会有丢失修改的风险,因此用小写的 -d 来删除分支的话力度不够,git要求用大写的 -D 来强制删除分支
git branch -D feature/zhanhzhong/dev
分支管理策略
开发原则
在git上开发时,需要保持一个基础原则:分支开发,主干发布。
方法1: master分支 + 开发者个人分支
这种简单一点,就是每个开发者在自己的个人分支上开发,开发完毕并且测试没问题后,就可以直接合并进master分支,然后进行发布。(一般快速迭代的后台适合用这种方式)
方法2: master分支 + dev分支 + 开发者个人分支
这种复杂一点,除了master分支外还有一个dev分支,每个开发者需要首先把自己的修改合并入dev分支,然后过一段时间后,进行大版本发布时,再一次性把dev分支合并入master分支中。(一般大型客户端平台比如微信等,会使用这种形式,因为不允许频繁更新客户端版本)
合并策略
git rebase和git merge这两个命令都旨在将更改代码从一个分支合并到另一个分支,但二者的合并方式却有很大的不同。总的来说,merge合并后不会破坏旧分支的commit记录,但是可能会产生额外的合并commit记录;rebase合并后会破坏旧分支的commit记录,但是不会产生额外的合并commit记录,而且可以形成线性的commit历史记录,非常直观。(用git log --graph命令看点线图)
- 融合代码到公共分支的时使用
git merge , 而不用git rebase - 融合代码到个人分支的时候使用
git rebase ,可以不污染分支的提交记录,形成简洁的线性提交历史记录。
因此下面的例子中,merge部分的举例是将个人分支合并到master分支中,rebase部分的距离是将master分支合并到个人分支。
merge
参考https://backlog.com/git-tutorial/cn/stepup/stepup1_4.html
情况1(Fast Forward): 从master上创建分支后,直到合并时,都没有其他开发者修改master分支的状态。
这种情况非常简单,直接把原本指向master最新commit结点的HEAD移动到分支的最新commit结点上就可以了。
由于这种方式非常流畅,所以叫做快进模式。分支合并时,git默认使用Fast Forward模式。
情况2: 从master上创建分支后,到合并时,已经有其他开发者修改过master分支的状态。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Iijfmtri-1639130586746)(https://raw.githubusercontent.com/zhongzhh8/Images/master/img/capture_stepup1_4_3.png)]
这种情况下需要将二者合并(无论是否需要解决冲突)会生产一个新的commit,然后将master上的HEAD移动到这个commit上。
情况三(--no-ff ):如果设定了non fast-forward选项(--no-ff 参数),那么即使在能够fast-forward合并的情况下也会生成新的提交并合并。
执行non fast-forward后,分支会维持原状。那么要查明在这个分支里的操作就很容易了。
rebase
参考【Git】:git rebase和git merge有什么区别?
如果是用merge方法将master分支合并到Feature分支,那么就会在Feature分支上新增一个commit结点,然后把HEAD移动到这个结点上。
而如果使用rebase方法,那么就相当于把Feature分支的所有commit都移动到目标分支的最新commit的后面,rebase会为Feature分支上的所有已有的commit都创建新的commit来重写commit记录这种方式叫做reapply(重放),即把已有的commit重新在代码上应用一遍。最后就形成线性的提交记录,可以更清晰的看到项目的历史提交。
总结
总的来说,
- git merge优点是合并后不破坏原分支的代码提交记录,缺点就是会产生额外的提交记录。由于远程master分支需要清晰的区分每个开发者的分支合并记录,所以开发者将Feature分支合并到master时应该使用merge方法。
- git rebase 优点是无须新增提交记录到目标分支,rebase后可以将对象分支的提交历史续上目标分支上,形成线性提交历史记录,进行review的时候更加直观。由于个人的Feature分支只有自己使用,所以怎么方便怎么来,所以开发者将远程master分支合并到本地的Feature分支时可以使用rebase方法。
git pull 与 git pull --rebase的区别
参考简单对比git pull和git pull --rebase的使用
开发者将远程master分支合并到本地的Feature分支的命令有两句:
git pull origin master
git pull --rebase origin master
二者的区别在于
git pull = git fetch + git merge
git pull --rebase = git fetch + git rebase
也就是说,git pull是用merge方法,git pull --rebase是用rebase方法,后者可以形成线性的提交记录,更加直观。
其他
stash暂存
开发的时候经常会出现这样的情况,我正在开发分支dev1,但是需要临时切换到分支dev2去修改一下代码,而此时必须将dev1上的内容git add和commit之后才能切换分支。但是此时dev1上还没开发完,还不想commit,怎么办呢?这个时候就可以用git的stash功能把当前工作现场暂存起来
git stash
可以看到此前修改的代码都不见了,再用git status查看工作区,可以看到工作区是干净的。
此时可以直接切换分支了,在分支dev2上开发完成后,再切换回分支dev1,查看暂存的修改、将暂存的修改恢复出来
git stash list
git stash pop
此时之前暂存的工作现场又恢复出来了,可以继续开发。
push -u 和 -f
-u参数有什么用
push和pull命令格式都是先写源分支,后写目的分支
git push <远程主机名> <本地分支名>:<远程分支名>
git pull <远程主机名> <远程分支名>:<本地分支名>
//git pull其实是由git fetch和git merge组成的,也可以先fetch下来看差异,再决定是否merge
push一般会省略远程分支名,直接用
git push <远程主机名> <本地分支名>
相当于将本地分支推送到同名的远程分支上,如果不存在同名的远程分支,则会新建一个这样的远程分支,然后把内容push过去。
第一次使用 git push -u origin master 之后,-u代表将origin设置为默认主机,那么以后就可以直接
- 使用 git pull 代替 git pull origin master 来拉取代码
- 使用 git push 代替 git push origin master 来推送代码
上面是设置了master分支与origin远程主机的master分支之间的关联,如果是其他分支,则也是一样的方法
git push -u origin feature/zhanhzhong/tapd1125
-f 参数
参考从程序员枪杀案谈git push -f
分支开发,主干合并,也可以分成两种情况:
1、本地feature分支开发完成后,push到远程的对应的feature分支上,然后再将远程feature分支与远程master合并。这种情况下,由于远程只有自己一个人在使用,那么只需要用git push命令就可以每次都正常的push上去。
2、本地feature分支开发完成后,先合并进本地master分支,然后再将本地master分支push到远程master分支上。这种情况下,由于远程master由很多人都在使用,因此有可能会出现本地master的版本比远程master版本落后(因为别的开发同学在我们之前往master里提交过代码),导致push失败,此时也有两种解决方法:
- 先把远程master pull到本地master上,解决了冲突,把本地master更新到最新版本,然后再push上去,一般都是使用这种方法。
- 强行用git push -f origin master将本地master覆盖到远程master上,这种情况下其他同学在master上的提交内容会丢失掉,所以一般不推荐使用这种方法。
因此可见,git push -f 是风险非常大的操作,很容易影响到别人的工作,比如其他同事修复了一个bug,但是远程master上的代码被你覆盖了,导致这个bug依旧存在而大家都以为已经修复了,这样就很容易造成公司财产的损失。
|