为什么我们要用git来提交代码?
我们平常在自己电脑上写一个简单的实验题目的时候,比如我实现了可视化功能,代码以及没有bug了,接下来想要同步数据库,但是又害怕接下来的代码会误改之前完整的代码,于是我就将当前没有Bug的代码压缩打包,命名为v1.0,然后就可以肆无忌惮的编写后面的代码了,等到后面的某一个功能实现之后,我重新将新版的代码压缩打包,命名为v1.1,继续后面的工作,如果后面某一个环节没做好导致所有代码都崩了,想回到v1.0,直接解压v1.0压缩包重新开始编写即可。
相信大家遇到一个复杂的项目的时候会有上述的操作,但是操作比较繁杂,特别是多个人来合作一个项目开发的时候,大家的代码共享、修改都会遇到很多的问题。使用git可以很好的解决上述问题,大家可以在自己的电脑上写好代码,然后上传到github 上,也可以将别人写的代码从github上下载过来,这个功能解决了开发人员之间的代码交互问题。
GitHub支持多个版本分支,如下图:
上图中每一个节点表示一个版本号的代码,假设左边分支名字为dev,表示我们进行开发的分支,也就是可能有bug,正在维护的版本分支;右边分支假设是main 分支,表示我们发布的版本分支,比如我们最终可以拿出来发布的版本。
main
分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;那在哪干活呢?干活都在dev
分支上,也就是说,dev
分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev
分支合并到main
上,在main
分支发布1.0版本.团队合作的分支看起来就像这样:
我首先在我的账号上创建了一个仓库BigData
,然后拉其他人过来就可以协同完成这个项目,首先将项目从远程拉取过来:
1 | git clone git@github.com:wangxl12/BigDataProject.git |
注意要把SSH Key添加到GitHub,如果提示电脑没有安装ssh,就需要安装一下.
查看目前有哪些分支:
1 | git branch |
默认只有main分支:
1 | git branch |
注意,要在dev分支上开发,就必须创建远程origin(远程主机名)的dev分支到本地,使用如下命令创建本地dev分支:
1 | git switch -c dev origin/dev |
然后就可以在dev分支上修改,并时不时将dev分支push到远程:
1 | git add . |
我一开始创建仓库的时候只有main分支,这个分支作为稳定版本发布的分支,尽量少用,而dev分支我们作为开发分支,可以上传代码到这里。可能对于本地仓库、远程仓库的概念有不理解的地方,可以参看下面这张图:
workspace就是我们的本地工作区,即BigData文件夹,index是暂存区,也在本地,repository是本地仓库,就是BigData目录下的.git目录(这是一个隐藏目录,如果看不见的话可能隐藏了,可以设置文件夹中的隐藏项为可见就可以看到了),而Remote就是远程仓库,即Github或者Gitee。有向线段之间的单词表示操作,经过这些操作之后文件会被传到不同的仓库。
廖雪峰教程
工作区(BigData)目录下有一个隐藏目录.git,这个是Git的版本库,版本库里有很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD.
往Git版本库里添加的时候分为两步:
第一步是用git add
将文件添加进去,实际上就是将文件中的修改添加到暂存区;
第二步是用git commit
提交修改,实际上是将暂存区中的所有内容提交到当前分支;
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master
分支,所以,现在,git commit
就是往master
分支上提交更改。可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。
比如现在创建了一个test.txt文件:
1 | touch test.txt |
先加到暂存区:
1 | git add test.txt |
然后提交到当前分支:
1 | git commit -m "我创建了一个test.txt文件" |
注意,git commit
只会将暂存区里的修改提交到当前的分支,如果有修改没有提交到暂存区,那么commit
无法将这个修改提交到当前分支。此时,使用git status
查看当前的状态的时候,可以看见类似下面的内容:
这就说明有一个read.txt文件在暂存区内,该文件有修改没有提交到版本库。可以使用如下指令查看工作区内的这个文件和版本库(本地)之间的区别:
1 | git diff HEAD -- readme.txt |
白色是工作区和版本库之间相同的部分,红色是工作区删掉的,绿色则是工作区添加的内容。
丢弃修改
- 假设此时修改了工作区的内容,但是还没有提交到暂存区,可以通过如下的方式丢弃修改:
丢弃工作区的修改:
1 | git restore readme.txt # 不用背,使用git status查看状态的时候有提示 |
修改之后查看工作区和版本库之间的区别:
发现回到了修改工作区之前的状态。就是让这个文件回到最近一次git commit
或git add
时的状态。git checkout -- file
命令中的--
很重要,没有--
,就变成了“切换到另一个分支”的命令。
- 假设此时修改了工作区的内容,还通过add添加到了暂存区,可以通过如下的方式丢弃修改:
首先输入git restore --staged <file>
将暂存区中的文件恢复到提交到暂存区之前,即在工作区修改了没有进行add操作。
然后使用git restore <file>
将工作区的文件修改抛弃,即恢复到没有修改的状态。期间可以使用git status查看文件的状态,如果是绿色字体的modified,表示提交到了暂存区,没有commit,如果是红色的modified,表示修改了没有add。
- 如果不但add交到了暂存区,还commit到了版本库,可以使用版本回退来回到上一个版本,不过这个要求你还没有将本地的版本库推送到远程版本库。
然后将本地仓库的内容上传到远程仓库:
1 | git push |
查看历史记录:
1 | git log # 确定要回到之前的哪个版本 |
版本回退:
1 | git reset --hard HEAD^ # 回退一个版本 |
版本前进:
1 | git reset --hard 版本号 # 版本号可以通过git log查看,不需要完全输入,可以通过输入4~5个字符自动找到 |
查看记录的每一次命令:
1 | git reflog # 确定要回到未来的哪个版本 |
删除文件
将readme.txt删除:
1 | rm readme.txt |
然后查看状态:git status
根据提示,可以使用git restore <file>
来恢复工作区的修改:
1 | git restore readme.txt |
重新查看状态:
1 | git status |
但是如果真的想删掉呢?我们创建一个test.txt文件试一下
1 | touch test.txt |
接下来将暂存区中的文件删掉,然后commit即可:
创建远程仓库
如果一开始只是在本地仓库中操作的,可以将本地仓库关联远程 仓库,然后将本地仓库的内容推送到GitHub仓库,在本地仓库下执行如下指令即可关联:
1 | git remote add origin git@github.com:wangxl12/learngit.git |
这里的origin是远程仓库,这是Git默认的叫法,也可以改成别的,但是origin
这个名字一看就知道是远程库。
下一步可以将本地库中的所有内容推送到远程库上:
1 | git push -u origin master |
把本地库的内容推送到远程,用git push
命令,实际上是把当前分支master
推送到远程。
由于远程库是空的,我们第一次推送master
分支时,加上了-u
参数,Git不但会把本地的master
分支内容推送的远程新的master
分支,还会把本地的master
分支和远程的master
分支关联起来,在以后的推送或者拉取时就可以简化命令。
从现在起,只要本地作了提交,就可以通过命令:
1 | git push origin master |
把本地master
分支的最新修改推送至GitHub,现在,你就拥有了真正的分布式版本库!
删除远程库
如果添加的时候地址写错了,或者就是想删除远程库,可以用git remote rm <name>
命令。使用前,建议先用git remote -v
查看远程库信息:
1 | git remote -v |
然后,根据名字删除,比如删除origin
:
1 | git remote rm origin |
此处的“删除”其实是解除了本地和远程的绑定关系,并不是物理上删除了远程库。远程库本身并没有任何改动。要真正删除远程库,需要登录到GitHub,在后台页面找到删除按钮再删除。
从远程库克隆
上面将的是先创建本地库,然后关联远程库,现在是先创建远程库,然后从远程库克隆。
如果有多个人协作开发,那么每个人各自从远程克隆一份就可以了。
1 | git clone git@github.com:michaelliao/gitskills.git |
分支管理
创建与合并分支
强烈建议先学廖雪峰的Git教程:https://www.liaoxuefeng.com/wiki/896043488029600/900003767775424
创建分支并切换分支:
1 | git checkout -b dev |
如果希望将分支推送到远程库可以使用如下指令:
1 | git push origin dev |
使用git branch
查看分支:
1 | git branch |
然后我们可以在dev分支上正常提交,比如对readme.txt添加了一行内容:
绿色部分就是添加的内容,然后提交:
1 | git add readme.txt |
通过git status也可以查看状态信息。
然后dev分支的工作完成之后,可以切回到main分支并查看添加的内容:
1 | git checkout main |
发现刚刚添加的内容不见了:
因为刚刚提交的是在dev 分支上,而main 分支此时的提交点并没有发生改变:
现在,我们将dev分支的工作成果合并到main上:
1 | git merge dev |
再查看readme.txt内容发现和dev分支的内容一致了:
注意到上面的Fast-forward
信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master
指向dev
的当前提交,所以合并速度非常快。
当然,也不是每次合并都能Fast-forward
,我们后面会讲其他方式的合并。
合并完成后,就可以放心地删除dev
分支了:
1 | git branch -d dev |
因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master
分支上工作效果是一样的,但过程更安全。
切换分支还可以使用switch:
1 | 创建并切换分支 |
查看分支:git branch
创建分支:git branch <name>
切换分支:git checkout <name>
或者git switch <name>
创建+切换分支:git checkout -b <name>
或者git switch -c <name>
合并某分支到当前分支(快速合并):git merge <name>
删除分支:git branch -d <name>
解决冲突
现在只有main分支,创建一个文件test,add、commit,然后创建一个新的分支feature1,add到暂存区,切换到这个分支下commit到本地仓库中:
1 | touch test |
至此,分支的状态到达了下图所示:
这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看:
1 | git merge feature1 |
因为我们对一个文件进行了两种修改,这两种修改谁覆盖谁都说不通,所以会发生冲突。我们查看冲突的文件:
1 | git status |
我们直接查看test的内容:
1 | cat test |
我们直接手动修改test文件为如下内容,从而解决冲突:
然后重新提交:
1 | git add test |
1 | git commit -m "conflict fixed" |
现在main和feature1分支变成了下图所示:
用带参数的git log
也可以看到分支的合并情况:
1 | git log --graph --pretty=oneline --abbrev-commit |
合并完成之后,我们就可以删除feature1分支了:
1 | git branch -d feature1 |
当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。
解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容,再提交。
用git log --graph
命令可以看到分支合并图。
上面举的例子是对同一个文件进行修改,所以无法自动合并,如果是两个分支同时创建一个文件的话,还是可以使用merge合并的,下面的内容就是举的这个例子:
然后再新建一个文件,add,切换到main分支下commit:
1 | touch test-main |
注意这里的创建文件、add在哪个分支下进行都可以,因为工作区和暂存区是所有分支共享的,但是commit需要在想提交的那个分支下进行,因为commit的本质就是同步工作区里的相应的分支。
现在可以切换到dev和main分支中查看,分别有一个test文件和test-main文件。现在各自的状态如下(将feature1分支改为dev,master分支改为main):
这种情况下如果进行快速合并的话:
1 | git switch main |
会调到一个类似文本文件的界面,输入:q退出。出来之后发现main分支有test文件了:
分支管理策略
通常,合并分支时,如果可能,Git会用Fast forward
模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward
模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
合并分支时,加上--no-ff
参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward
合并就看不出来曾经做过合并。
切换到dev分支,修改一下test里的内容,然后add, commit提交:
1 | git switch -c dev |
然后切换回main:
1 | git switch main |
准备合并dev分支,请注意–no-ff参数,表示禁用Fast forward:
1 | git switch main |
1 | git merge --no-ff -m "merge with no-ff" dev # 因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去。 |
合并后,我们用git log
看看分支历史:
1 | git log --graph --pretty=oneline --abbrev-commit |
可以看到,不使用Fast forward
模式,merge后就像这样: